Version 1.4.7

New bombsAway module
This commit is contained in:
Christian Franz 2023-11-02 12:36:51 +01:00
parent 08527a515d
commit c3ba1e7c05
17 changed files with 1354 additions and 375 deletions

Binary file not shown.

Binary file not shown.

View File

@ -22,8 +22,6 @@ FARPZones.verbose = false
FARPZones.requiredLibs = { FARPZones.requiredLibs = {
"dcsCommon", "dcsCommon",
"cfxZones", -- Zones, of course "cfxZones", -- Zones, of course
-- "cfxCommander", -- to make troops do stuff
-- "cfxGroundTroops", -- generic when dropping troops
} }
-- *** DOES NOT EXTEND ZONES, USES OWN STRUCT *** -- *** DOES NOT EXTEND ZONES, USES OWN STRUCT ***

385
modules/TDZ.lua Normal file
View File

@ -0,0 +1,385 @@
tdz = {}
tdz.version = "0.9.0dev"
tdz.requiredLibs = {
"dcsCommon", -- always
"cfxZones", -- Zones, of course
}
tdz.allTdz = {}
tdz.watchlist = {}
tdz.watching = false
tdz.timeoutAfter = 120 -- seconds.
--
-- rwy draw procs
--
function tdz.rotateXZPolyInRads(thePoly, rads)
if not rads then
trigger.action.outText("rotateXZPolyInRads (inner): no rads", 30)
return
end
local c = math.cos(rads)
local s = math.sin(rads)
for idx, p in pairs(thePoly) do
local nx = p.x * c - p.z * s
local nz = p.x * s + p.z * c
p.x = nx
p.z = nz
end
end
function tdz.rotateXZPolyAroundCenterInRads(thePoly, center, rads)
if not rads then
trigger.action.outText("rotateXZPolyAroundCenterInRads: no rads", 30)
return
end
local negCtr = {x = -center.x, y = -center.y, z = -center.z}
tdz.translatePoly(thePoly, negCtr)
if not rads then
trigger.action.outText("WHOA! rotateXZPolyAroundCenterInRads: no rads", 30)
return
end
tdz.rotateXZPolyInRads(thePoly, rads)
tdz.translatePoly(thePoly, center)
end
function tdz.rotateXZPolyAroundCenterInDegrees(thePoly, center, degrees)
tdz.rotateXZPolyAroundCenterInRads(thePoly, center, degrees * 0.0174533)
end
function tdz.translatePoly(thePoly, v) -- straight rot, translate to 0 first
for idx, aPoint in pairs(thePoly) do
aPoint.x = aPoint.x + v.x
if aPoint.y then aPoint.y = aPoint.y + v.y end
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
-- create a 0-rotated centered poly
local poly = {}
local half = length / 2
local leftEdge = -half
poly[1] = { x = leftEdge + a * length, z = width / 2, y = 0}
poly[2] = { x = leftEdge + b * length, z = width / 2, y = 0}
poly[3] = { x = leftEdge + b * length, z = -width / 2, y = 0}
poly[4] = { 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)
-- make it a dml zone
local theNewZone = cfxZones.createSimplePolyZone(name, center, poly)
return theNewZone--, left, right
end
--
-- create a tdz
--
function tdz.createTDZ(theZone)
local p = theZone:getPoint()
local theBase = dcsCommon.getClosestAirbaseTo(p) -- never get FARPS
theZone.base = theBase
theZone.baseName = theBase:getName()
-- get closest runway to TDZ
-- may get a bit hairy, so let's find a good way
local allRwys = theBase:getRunways()
local nearestRwy = nil
local minDist = math.huge
for idx, aRwy in pairs(allRwys) do
local rp = aRwy.position
local dist = dcsCommon.distFlat(p, rp)
if dist < minDist then
nearestRwy = aRwy
minDist = dist
end
end
local bearing = nearestRwy.course * (-1)
theZone.bearing = bearing
rwname = math.floor(dcsCommon.bearing2degrees(bearing)/10 + 0.5) -- nice number
degrees = math.floor(dcsCommon.bearing2degrees(bearing) * 10) / 10
if degrees < 0 then degrees = degrees + 360 end
if degrees > 360 then degrees = degrees - 360 end
if rwname < 0 then rwname = rwname + 36 end
if rwname > 36 then rwname = rwname - 36 end
local opName = rwname + 18
if opName > 36 then opName = opName - 36 end
if rwname < 10 then rwname = "0"..rwname end
if opName < 10 then opName = "0" .. opName end
theZone.rwName = rwname .. "/" .. opName
theZone.opName = opName .. "/" .. rwname
local rwLen = nearestRwy.length
local rwWid = nearestRwy.width
local pos = nearestRwy.position
-- p1 is for distance to centerline calculation, defining a point
-- length away in direction bearing, setting up the line
-- theZone.rwCenter, theZone.p1
theZone.rwCenter = pos
local p1 = {x = pos.x + math.cos(bearing) * rwLen, y = 0, z = pos.z + math.sin(bearing) * rwLen}
theZone.rwP1 = p1
theZone.starts = theZone:getNumberFromZoneProperty("starts", 0)
theZone.ends = theZone:getNumberFromZoneProperty("ends", 610) -- m = 2000 ft
theZone.opposing = theZone:getBoolFromZoneProperty("opposing", true)
theZone.runwayZone = tdz.calcTDZone(theZone.name .. "-" .. rwname .. "main", pos, rwLen, rwWid, bearing)
theZone.runwayZone:drawZone({0, 0, 0, 1}, {0, 0, 0, 0}) -- black outline
local theTDZone = tdz.calcTDZone(theZone.name .. "-" .. rwname, pos, rwLen, rwWid, bearing, theZone.starts / rwLen, theZone.ends/rwLen)
-- to do: mark the various zones of excellence in different colors, or at least the excellent one with more color
theTDZone:drawZone({0, 1, 0, 1}, {0, 1, 0, .25})
theZone.normTDZone = theTDZone
if theZone.opposing then
theTDZone = tdz.calcTDZone(theZone.name .. "-" .. opName, pos, rwLen, rwWid, bearing + math.pi, theZone.starts / rwLen, theZone.ends/rwLen)
theTDZone:drawZone({0, 1, 0, 1}, {0, 1, 0, .25})
theZone.opTDZone = theTDZone
theZone.opBearing = bearing + math.pi
end
if theZone:hasProperty("landed!") then
theZone.landedFlag = theZone:getStringFromZoneProperty("landed!", "none")
end
if theZone:hasProperty("touchdown!") then
theZone.touchDownFlag = theZone:getStringFromZoneProperty("touchDown!", "none")
end
if theZone:hasProperty("fail!") then
theZone.failFlag = theZone:getStringFromZoneProperty("fail!", "none")
end
theZone.method = theZone:getStringFromZoneProperty("method", "inc")
end
--
-- event handler
--
function tdz.playerLanded(theUnit, playerName)
if tdz.watchlist[playerName] then
-- this is not a new landing, for now ignore, increment bump count
-- 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
-- see if we touched down inside of one of our watched zones
local p = theUnit:getPoint()
local theGroup = theUnit:getGroup()
local gID = theGroup:getID()
local msg = ""
local theZone = nil
for idx, aRunway in pairs(tdz.allTdz) do
local theRunway = aRunway.runwayZone
if theRunway:pointInZone(p) then
-- touchdown!
theZone = aRunway
if theZone.touchDownFlag then
theZone.pollFlag(theZone.touchDownFlag, theZone.method)
end
trigger.action.outTextForGroup(gID, "Touchdown! Come to a FULL STOP for evaluation", 30)
end
end
if not theZone then return end -- no landing eval zone hit
-- start a new watchlist entry
local entry = {}
entry.msg = ""
entry.playerName = playerName
entry.unitName = theUnit:getName()
entry.theType = theUnit:getTypeName()
entry.gID = gID
entry.theTime = timer.getTime()
entry.tdPoint = p
entry.tdVel = theUnit:getVelocity() -- vector
entry.hops = 1
entry.theZone = theZone
-- see if we are in main or opposite direction
local hdg = dcsCommon.getUnitHeading(theUnit)
local dHdg = math.abs(theZone.bearing - hdg) -- 0..Pi
local dOpHdg = math.abs(theZone.opBearing - hdg)
local opposite = false
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
elseif dHdg > math.pi / 2 then -- > 90+
dHdg = dHdg - math.pi / 2
end
dHdg = math.floor(dHdg * 572.958) / 10 -- in degrees
local lHdg = math.floor(hdg * 572.958) / 10 -- also in deg
-- now see how far off centerline.
local offcenter = dcsCommon.distanceOfPointPToLineXZ(p, theZone.rwCenter, theZone.rwP1)
offcenter = math.floor(offcenter * 10)/10
local vel = dcsCommon.vMag(entry.tdVel)
local vkm = math.floor(vel * 36) / 10
local kkm = math.floor(vel * 19.4383) / 10
entry.msg = entry.msg .. "\nLanded heading " .. lHdg .. "°, diverging by " .. dHdg .. "° from runway heading, velocity at touchdown " .. vkm .. " kmh/" .. kkm .. " kts, touchdown " .. offcenter .. " m off centerline\n"
-- inside TDZ?
local tdZone = theZone.normTDZone
if opposite and theZone.opposing then
tdZone = theZone.opTDZone
end
if tdZone:pointInZone(p) then
-- yes, how far behind threshold
-- project point onto line to see how far inside
local distBehind = dcsCommon.distanceOfPointPToLineXZ(p, tdZone.poly[1], tdZone.poly[4])
local zonelen = math.abs(theZone.starts-theZone.ends)
local percentile = math.floor(distBehind / zonelen * 100)
local rating = ""
if percentile < 5 or percentile > 90 then rating = "marginal"
elseif percentile < 15 or percentile > 80 then rating = "pass"
elseif percentile < 25 or percentile > 60 then rating = "good"
else rating = "excellent" end
entry.msg = entry.msg .. "Touchdown inside TD-Zone, <" .. math.floor(distBehind) .. " m> behind threshold, rating = " .. rating .. "\n"
end
tdz.watchlist[playerName] = entry
if not tdz.watching then
tdz.watching = true
timer.scheduleFunction(tdz.watchLandings, {}, timer.getTime() + 0.2)
end
end
function tdz:onEvent(event)
if not event.initiator then return end
local theUnit = event.initiator
if not theUnit.getPlayerName then return end
local playerName = theUnit:getPlayerName()
if not playerName then return end
if event.id == 4 then
-- player landed
tdz.playerLanded(theUnit, playerName)
end
end
--
-- Monitor landings in progress
--
function tdz.watchLandings()
local filtered = {}
local count = 0
local transfer = false
local success = false
local now = timer.getTime()
for playerName, aLanding in pairs (tdz.watchlist) do
-- see if landing timed out
local tdiff = now - aLanding.theTime
if tdiff < tdz.timeoutAfter then
local theUnit = Unit.getByName(aLanding.unitName)
if theUnit and Unit.isExist(theUnit) then
local vel = theUnit:getVelocity()
local vel = dcsCommon.vMag(vel)
local p = theUnit:getPoint()
if aLanding.theZone.runwayZone:pointInZone(p) then
-- we must slow down to below 3.6 km/h
if vel < 1 then
-- make sure that we are still inside the runway
success = true
else
transfer = true
end
else
trigger.action.outTextForGroup(aLanding.gID, "Ran off runway.", 30)
end
end
end
if transfer then
count = count + 1
filtered[playerName] = aLanding
else
local theZone = aLanding.theZone
if success then
local theUnit = Unit.getByName(aLanding.unitName)
local p = theUnit:getPoint()
local tdist = math.floor(dcsCommon.distFlat(p, aLanding.tdPoint))
aLanding.msg = aLanding.msg .."\nSuccessful landing for " .. aLanding.playerName .." in a " .. aLanding.theType .. ". Landing run = <" .. tdist .. " m>, <" .. math.floor(tdiff*10)/10 .. "> seconds from touch-down to standstill."
if aLanding.hops > 1 then
aLanding.msg = aLanding.msg .. "\nNumber of hops: " .. aLanding.hops
end
if theZone.landedFlag then
theZone:pollFlag(theZone.landedFlag, theZone.method)
end
aLanding.msg = aLanding.msg .."\n"
trigger.action.outTextForGroup(aLanding.gID, aLanding.msg, 30)
else
if theZone.failFlag then
theZone:pollFlag(theZone.failFlag, theZone.method)
end
trigger.action.outTextForGroup(aLanding.gID, "Landing for " .. aLanding.playerName .." incomplete.", 30)
end
end
end
tdz.watchlist = filtered
if count > 0 then
timer.scheduleFunction(tdz.watchLandings, {}, timer.getTime() + 0.2)
else
tdz.watching = false
end
end
--
-- Start
--
function tdz.readConfigZone()
end
function tdz.start()
if not dcsCommon.libCheck("cfx TDZ",
tdz.requiredLibs) then
return false
end
-- read config
tdz.readConfigZone()
-- collect all wp target zones
local attrZones = cfxZones.getZonesWithAttributeNamed("TDZ")
for k, aZone in pairs(attrZones) do
tdz.createTDZ(aZone) -- process attribute and add to zone
table.insert(tdz.allTdz, aZone) -- remember it so we can smoke it
end
-- add event handler
world.addEventHandler(tdz)
trigger.action.outText("cf/x TDZ version " .. tdz.version .. " running", 30)
return true
end
if not tdz.start() then
trigger.action.outText("cf/x TDZ aborted: missing libraries", 30)
tdz = nil
end

564
modules/bombRange.lua Normal file
View File

@ -0,0 +1,564 @@
bombRange = {}
bombRange.version = "1.0.0"
bombRange.dh = 1 -- meters above ground level burst
bombRange.requiredLibs = {
"dcsCommon", -- always
"cfxZones", -- Zones, of course
}
bombRange.bombs = {} -- live tracking
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
function bombRange.addBomb(theBomb)
table.insert(bombRange.bombs, theBomb)
end
function bombRange.addRange(theZone)
table.insert(bombRange.ranges, theZone)
end
function bombRange.markRange(theZone)
local newObjects = theZone:markZoneWithObjects(theZone.markType, theZone.markNum, false)
for idx, aStatic in pairs(newObjects) do
local theID = tonumber(aStatic:getID())
bombRange.myStatics[theID] = aStatic
end
end
function bombRange.markCenter(theZone)
local theObject = theZone:markCenterWithObject(theZone.centerType)
--table.insert(bombRange.myStatics, theObject)
local theID = tonumber(theObject:getID())
bombRange.myStatics[theID] = theObject
end
function bombRange.createRange(theZone) -- has bombRange attribte to mark it
theZone.usePercentage = theZone:getBoolFromZoneProperty("percentage", theZone.isCircle)
if theZone.usePercentage and theZone.isPoly then
trigger.action.outText("+++bRng: WARNING: zone <" .. theZone.name .. "> is not a circular zone but wants to use percentage scoring!", 30)
end
theZone.details = theZone:getBoolFromZoneProperty("details", false)
theZone.reporter = theZone:getBoolFromZoneProperty("reporter", true)
theZone.reportName = theZone:getBoolFromZoneProperty("reportName", true)
theZone.smokeHits = theZone:getBoolFromZoneProperty("smokeHits", false)
theZone.smokeColor = theZone:getSmokeColorStringFromZoneProperty("smokeColor", "blue")
theZone.flagHits = theZone:getBoolFromZoneProperty("flagHits", false)
theZone.flagType = theZone:getStringFromZoneProperty("flagType", "Red_Flag")
theZone.clipDist = theZone:getNumberFromZoneProperty("clipDist", 2000) -- when further way, the drop will be disregarded
theZone.method = theZone:getStringFromZoneProperty("method", "inc")
if theZone:hasProperty("hit!") then
theZone.hitOut = theZone:getStringFromZoneProperty("hit!", "<none>")
end
theZone.markType = theZone:getStringFromZoneProperty("markType", "Black_Tyre_RF")
theZone.markBoundary = theZone:getBoolFromZoneProperty("markBoundary", false)
theZone.markNum = theZone:getNumberFromZoneProperty("markNum", 3) -- per quarter
theZone.markCenter = theZone:getBoolFromZoneProperty("markCenter", false)
theZone.centerType = theZone:getStringFromZoneProperty("centerType", "house2arm")
theZone.markOnMap = theZone:getBoolFromZoneProperty("markOnMap", false)
theZone.mapColor = theZone:getRGBAVectorFromZoneProperty("mapColor", {0.8, 0.8, 0.8, 1.0})
theZone.mapFillColor = theZone:getRGBAVectorFromZoneProperty("mapFillColor", {0.8, 0.8, 0.8, 0.2})
if theZone.markBoundary then bombRange.markRange(theZone) end
if theZone.markCenter then bombRange.markCenter(theZone) end
if theZone.markOnMap then
local markID = theZone:drawZone(theZone.mapColor, theZone.mapFillColor)
end
end
--
-- player data
--
function bombRange.getPlayerData(name)
local theData = bombRange.playerData[name]
if not theData then
theData = {}
theData.aircraft = {} -- by typeDesc contains all drops per weapon type
theData.totalDrops = 0
theData.totalHits = 0
theData.totalPercentage = 0 -- sum, must be divided by drops
bombRange.playerData[name] = theData
-- trigger.action.outText("created new player data for " .. name, 30)
end
return theData
end
function bombRange.addImpactForWeapon(weapon, isInside, percentage)
if not percentage then percentage = 0 end
if type(percentage) == "string" then percentage = 1 end -- handle poly
local theData = bombRange.getPlayerData(weapon.pName)
local uType = weapon.uType
local uData = theData.aircraft[uType]
if not uData then
uData = {}
uData.wTypes = {}
theData.aircraft[uType] = uData
end
wType = weapon.type
local wData = uData.wTypes[wType]
if not wData then
wData = {shots = 0, hits = 0, percentage = 0}
uData.wTypes[wType] = wData
end
wData.shots = wData.shots + 1
if isInside then
wData.hits = wData.hits + 1
wData.percentage = wData.percentage + percentage
else
end
theData.totalDrops = theData.totalDrops + 1
if isInside then
theData.totalHits = theData.totalHits + 1
theData.totalPercentage = theData.totalPercentage + percentage
end
end
function bombRange.showStatsForPlayer(pName, gID, unitName)
local theData = bombRange.getPlayerData(pName)
local msg = "\nWeapons Range Statistics for " .. pName .. "\n"
local lineCount = 0
for aType, aircraft in pairs(theData.aircraft) do
if aircraft.wTypes then
if lineCount < 1 then
msg = msg .. " Aircraft / Munition : Drops / Hits / Quality\n"
end
for wName, wData in pairs(aircraft.wTypes) do
local pct = wData.percentage / wData.shots
pct = math.floor(pct * 10) / 10
msg = msg .. " " .. aType .. " / " .. wName .. ": " .. wData.shots .. " / " .. wData.hits .. " / " .. pct .. "%\n"
lineCount = lineCount + 1
end
end -- if weapon per aircraft
end
if lineCount < 1 then
msg = msg .. "\n NO DATA\n\n"
else
msg = msg .. "\n Total ordnance drops: " .. theData.totalDrops
msg = msg .. "\n Total on target: " .. theData.totalHits
local q = math.floor(theData.totalPercentage / theData.totalDrops * 10) / 10
msg = msg .. "\n Total Quality: " .. q .. "%\n"
end
if bombRange.mustCheckIn then
local comms = bombRange.unitComms[unitName]
if comms.checkedIn then
msg = msg .. "\nYou are checked in with weapons range command.\n"
else
msg = msg .. "\nPLEASE CHECK IN with weapons range command.\n"
end
end
trigger.action.outTextForGroup(gID, msg, 30)
end
--
-- unit UI
--
function bombRange.initCommsForUnit(theUnit)
local uName = theUnit:getName()
local pName = theUnit:getPlayerName()
local theGroup = theUnit:getGroup()
local gID = theGroup:getID()
local comms = bombRange.unitComms[uName]
if comms then
if bombRange.mustCheckIn then
missionCommands.removeItemForGroup(gID, comms.checkin)
end
missionCommands.removeItemForGroup(gID, comms.reset)
missionCommands.removeItemForGroup(gID, comms.getStat)
missionCommands.removeItemForGroup(gID, comms.root)
end
comms = {}
comms.checkedIn = false
comms.root = missionCommands.addSubMenuForGroup(gID, bombRange.menuTitle)
comms.getStat = missionCommands.addCommandForGroup(gID, "Get statistics for " .. pName, comms.root, bombRange.redirectComms, {"getStat", uName, pName, gID})
comms.reset = missionCommands.addCommandForGroup(gID, "RESET statistics for " .. pName, comms.root, bombRange.redirectComms, {"reset", uName, pName, gID})
if bombRange.mustCheckIn then
comms.checkin = missionCommands.addCommandForGroup(gID, "Check in with range", comms.root, bombRange.redirectComms, {"check", uName, pName, gID})
end
bombRange.unitComms[uName] = comms
end
function bombRange.redirectComms(args)
timer.scheduleFunction(bombRange.commsRequest, args, timer.getTime() + 0.1)
end
function bombRange.commsRequest(args)
local command = args[1] -- getStat, check,
local uName = args[2]
local pName = args[3]
local theUnit = Unit.getByName(uName)
local theGroup = theUnit:getGroup()
local gID = theGroup:getID()
if command == "getStat" then
bombRange.showStatsForPlayer(pName, gID, uName)
end
if command == "reset" then
bombRange.playerData[pName] = nil
trigger.action.outTextForGroup(gID, "Clean slate, " .. uName .. ", all existing records have been deleted.", 30)
end
if command == "check" then
comms = bombRange.unitComms[uName]
if comms.checkedIn then
comms.checkedIn = false -- we are now checked out
missionCommands.removeItemForGroup(gID, comms.checkin)
comms.checkin = missionCommands.addCommandForGroup(gID, "Check in with range", comms.root, bombRange.redirectComms, {"check", uName, pName, gID})
trigger.action.outTextForGroup(gID, "Roger, " .. uName .. ", terminating range advisory. Have a good day!", 30)
if bombRange.signOut then
cfxZones.pollFlag(bombRange.signOut, bombRange.method, bombRange)
end
else
comms.checkedIn = true
missionCommands.removeItemForGroup(gID, comms.checkin)
comms.checkin = missionCommands.addCommandForGroup(gID, "Check OUT " .. uName .. " from range", comms.root, bombRange.redirectComms, {"check", uName, pName, gID})trigger.action.outTextForGroup(gID, uName .. ", you are go for weapons deployment, observers standing by.", 30)
if bombRange.signIn then
cfxZones.pollFlag(bombRange.signIn, bombRange.method, bombRange)
end
end
end
end
--
-- Event Proccing
--
function bombRange.suspectedHit(weapon, target)
if not bombRange.tracking then
return
end
if not target then return end
local theType = target:getTypeName()
for idx, aType in pairs(bombRange.filterTypes) do
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
end
-- look through the tracked weapons for a match
local filtered = {}
local hasfound = false
for idx, b in pairs (bombRange.bombs) do
if b.weapon == weapon then
hasfound = true
-- update b to current position and velocity
b.pos = weapon:getPoint()
b.v = weapon:getVelocity()
bombRange.impacted(b, target)
else
table.insert(filtered, b)
end
end
if hasfound then
bombRange.bombs = filtered
end
end
function bombRange:onEvent(event)
if not event.initiator then return end
local theUnit = event.initiator
if event.id == 2 then -- hit
if not event.weapon then return end
bombRange.suspectedHit(event.weapon, event.target)
return
end
local uName = nil
local pName = nil
if theUnit.getPlayerName and theUnit:getPlayerName() ~= nil then
uName = theUnit:getName()
pName = theUnit:getPlayerName()
else return end
if event.id == 1 then -- shot event, from player
if not event.weapon then return end
local uComms = bombRange.unitComms[uName]
if bombRange.mustCheckIn and (not uComms.checkedIn) then
if bombRange.verbose then
trigger.action.outText("+++bRng: Player <" .. pName .. "> not checked in.", 30)
end
return
end
local w = event.weapon
local b = {}
local bName = w:getName()
b.name = bName
b.type = w:getTypeName()
-- may need to verify type: how do we handle clusters or flares?
b.pos = w:getPoint()
b.v = w:getVelocity()
b.pName = pName
b.uName = uName
b.uType = theUnit:getTypeName()
b.gID = theUnit:getGroup():getID()
b.weapon = w
b.released = timer.getTime()
b.relPos = b.pos
table.insert(bombRange.bombs, b)
if not bombRange.tracking then
timer.scheduleFunction(bombRange.updateBombs, {}, timer.getTime() + 1/bombRange.ups)
bombRange.tracking = true
if bombRange.verbose then
trigger.action.outText("+++bRng: start tracking.", 30)
end
end
if bombRange.verbose then
trigger.action.outText("+++bRng: Player <" .. pName .. "> fired a <" .. b.type .. ">, named <" .. b.name .. ">", 30)
end
end
if event.id == 15 then
bombRange.initCommsForUnit(theUnit)
end
end
--
-- Update
--
function bombRange.impacted(weapon, target)
local targetName = nil
local ipos = weapon.pos -- default to weapon location
if target then
ipos = target:getPoint()
targetName = target:getDesc()
if targetName then targetName = targetName.displayName end
if not targetName then targetName = target:getTypeName() end
else
-- not an object hit, interpolate the impact point on ground:
-- calculate impact point. we use the linear equation
-- pos.y + t*velocity.y - height = 1 (height above gnd) and solve for t
local h = land.getHeight({x=weapon.pos.x, y=weapon.pos.z}) - bombRange.dh -- dh m above gnd
local t = (h-weapon.pos.y) / weapon.v.y
-- having t, we project location using pos and vel
-- impactpos = pos + t * velocity
local imod = dcsCommon.vMultScalar(weapon.v, t)
ipos = dcsCommon.vAdd(weapon.pos, imod) -- calculated impact point
end
-- see if inside a range
if #bombRange.ranges < 1 then
trigger.action.outText("+++bRng: No Bomb Ranges detected!")
return -- no need to update anything
end
local minDist = math.huge
local theRange = nil
for idx, theZone in pairs(bombRange.ranges) do
local p = theZone:getPoint()
local dist = dcsCommon.distFlat(p, ipos)
if dist < minDist then
minDist = dist
theRange = theZone
end
end
if not theRange then
trigger.action.outText("+++bRng: nil <theRange> 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
trigger.action.outTextForGroup(weapon.gID, "Impact of <" .. weapon.type .. "> released by <" .. weapon.pName .. "> outside bomb range and disregarded.", 30)
end
return
end
if theRange.smokeHits then
trigger.action.smoke(ipos, theRange.smokeColor)
end
if (not target) and theRange.flagHits then -- only ground imparts 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)
dcsCommon.moveStaticDataTo(theStaticData, p.x, p.y)
local theObject = coalition.addStaticObject(cty, theStaticData)
end
if theRange.reporter and theRange.details then
local t = math.floor((timer.getTime() - 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
trigger.action.outTextForGroup(weapon.gID, "impact of " .. weapon.type .. " released by " .. weapon.pName .. " from " .. weapon.uType .. " after traveling " .. tDist .. " km in " .. t .. " sec, impact velocity at impact is " .. v .. " m/s!", 30)
end
local msg = ""
if theRange:pointInZone(ipos) then
local percentage = 0
if theRange.isPoly then
percentage = 100
else
percentage = 1 - (minDist / theRange.radius)
percentage = math.floor(percentage * 100)
end
msg = "INSIDE target area"
if theRange.reportName then msg = msg .. " " .. theRange.name end
if (not targetName) and theRange.details then msg = msg .. ", off-center by " .. math.floor(minDist *10)/10 .. " m" end
if targetName then msg = msg .. ", hit on " .. targetName end
if not theRange.usePercentage then
percentage = 100
else
msg = msg .. " (Quality " .. percentage .."%)"
end
if theRange.hitOut then
theZone:pollFlag(theRange.hitOut, theRange.method)
end
bombRange.addImpactForWeapon(weapon, true, percentage)
else
msg = "Outside target area"
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
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.updateBombs()
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()
table.insert(filtered, theWeapon)
else
-- interpolate the impact position from last position
bombRange.impacted(theWeapon)
end
end
bombRange.bombs = filtered
if #filtered > 0 then
timer.scheduleFunction(bombRange.updateBombs, {}, timer.getTime() + 1/bombRange.ups)
bombRange.tracking = true
else
bombRange.tracking = false
if bombRange.verbose then
trigger.action.outText("+++bRng: stopped tracking.", 30)
end
end
end
--
-- load & save data
--
function bombRange.saveData()
local theData = {}
-- save current score list. simple clone
local theStats = dcsCommon.clone(bombRange.playerData)
theData.theStats = theStats
return theData
end
function bombRange.loadData()
if not persistence then return end
local theData = persistence.getSavedDataForModule("bombRange")
if not theData then
if bombRange.verbose then
trigger.action.outText("+++bRng: no save date received, skipping.", 30)
end
return
end
local theStats = theData.theStats
bombRange.playerData = theStats
end
--
-- Config & Start
--
function bombRange.readConfigZone()
bombRange.name = "bombRangeConfig"
local theZone = cfxZones.getZoneByName("bombRangeConfig")
if not theZone then
theZone = cfxZones.createSimpleZone("bombRangeConfig")
end
local theSet = theZone:getStringFromZoneProperty("filterTypes", "house2arm, Black_Tyre_RF, Red_Flag")
theSet = dcsCommon.splitString(theSet, ",")
bombRange.filterTypes = dcsCommon.trimArray(theSet)
bombRange.reportLongMisses = theZone:getBoolFromZoneProperty("reportLongMisses", false)
bombRange.mustCheckIn = theZone:getBoolFromZoneProperty("mustCheckIn", false)
bombRange.ups = theZone:getNumberFromZoneProperty("ups", 20)
bombRange.menuTitle = theZone:getStringFromZoneProperty("menuTitle","Contact BOMB RANGE")
if theZone:hasProperty("signIn!") then
bombRange.signIn = theZone:getStringFromZoneProperty("signIn!", 30)
end
if theZone:hasProperty("signOut!") then
bombRange.signOut = theZone:getStringFromZoneProperty("signOut!", 30)
end
bombRange.method = theZone:getStringFromZoneProperty("method", "inc")
bombRange.verbose = theZone.verbose
end
function bombRange.start()
if not dcsCommon.libCheck("cfx bombRange",
bombRange.requiredLibs) then
return false
end
-- read config
bombRange.readConfigZone()
-- collect all wp target zones
local attrZones = cfxZones.getZonesWithAttributeNamed("bombRange")
for k, aZone in pairs(attrZones) do
bombRange.createRange(aZone) -- process attribute and add to zone
bombRange.addRange(aZone) -- remember it so we can smoke it
end
-- load data
if persistence then
-- sign up for persistence
callbacks = {}
callbacks.persistData = bombRange.saveData
persistence.registerModule("bombRange", callbacks)
-- now load my data
bombRange.loadData()
end
-- add event handler
world.addEventHandler(bombRange)
return true
end
if not bombRange.start() then
trigger.action.outText("cf/x Bomb Range aborted: missing libraries", 30)
bombRange = nil
end
--
-- add persistence
--

View File

@ -1,5 +1,5 @@
cfxNDB = {} cfxNDB = {}
cfxNDB.version = "1.2.1" cfxNDB.version = "1.3.0"
--[[-- --[[--
cfxNDB: cfxNDB:
@ -26,6 +26,7 @@ cfxNDB.version = "1.2.1"
- update only when moving and delta > maxDelta - update only when moving and delta > maxDelta
- zone-local verbosity support - zone-local verbosity support
- better config defaulting - better config defaulting
1.3.0 - dmlZones
--]]-- --]]--
@ -104,42 +105,42 @@ function cfxNDB.stopNDB(theNDB)
end end
function cfxNDB.createNDBWithZone(theZone) function cfxNDB.createNDBWithZone(theZone)
theZone.freq = cfxZones.getNumberFromZoneProperty(theZone, "NDB", 124) -- in MHz theZone.freq = theZone:getNumberFromZoneProperty("NDB", 124) -- in MHz
-- convert MHz to Hz -- convert MHz to Hz
theZone.freq = theZone.freq * 1000000 -- Hz theZone.freq = theZone.freq * 1000000 -- Hz
theZone.fm = cfxZones.getBoolFromZoneProperty(theZone, "fm", false) theZone.fm = theZone:getBoolFromZoneProperty("fm", false)
theZone.ndbSound = cfxZones.getStringFromZoneProperty(theZone, "soundFile", "<none>") theZone.ndbSound = theZone:getStringFromZoneProperty("soundFile", "<none>")
theZone.power = cfxZones.getNumberFromZoneProperty(theZone, "watts", cfxNDB.power) theZone.power = theZone:getNumberFromZoneProperty("watts", cfxNDB.power)
theZone.loop = true -- always. NDB always loops theZone.loop = true -- always. NDB always loops
-- UNSUPPORTED refresh. Although read individually, it only works -- UNSUPPORTED refresh. Although read individually, it only works
-- when LARGER than module's refresh. -- when LARGER than module's refresh.
theZone.ndbRefresh = cfxZones.getNumberFromZoneProperty(theZone, "ndbRefresh", cfxNDB.refresh) -- only used if linked theZone.ndbRefresh = theZone:getNumberFromZoneProperty("ndbRefresh", cfxNDB.refresh) -- only used if linked
theZone.ndbRefreshTime = timer.getTime() + theZone.ndbRefresh -- only used with linkedUnit, but set up nonetheless theZone.ndbRefreshTime = timer.getTime() + theZone.ndbRefresh -- only used with linkedUnit, but set up nonetheless
-- paused -- paused
theZone.paused = cfxZones.getBoolFromZoneProperty(theZone, "paused", false) theZone.paused = theZone:getBoolFromZoneProperty("paused", false)
-- watchflags -- watchflags
theZone.ndbTriggerMethod = cfxZones.getStringFromZoneProperty(theZone, "triggerMethod", "change") theZone.ndbTriggerMethod = theZone:getStringFromZoneProperty( "triggerMethod", "change")
if cfxZones.hasProperty(theZone, "ndbTriggerMethod") then if theZone:hasProperty("ndbTriggerMethod") then
theZone.ndbTriggerMethod = cfxZones.getStringFromZoneProperty(theZone, "ndbTriggerMethod", "change") theZone.ndbTriggerMethod = theZone:getStringFromZoneProperty("ndbTriggerMethod", "change")
end end
-- on/offf query flags -- on/offf query flags
if cfxZones.hasProperty(theZone, "on?") then if theZone:hasProperty("on?") then
theZone.onFlag = cfxZones.getStringFromZoneProperty(theZone, "on?", "none") theZone.onFlag = theZone:getStringFromZoneProperty("on?", "none")
end end
if theZone.onFlag then if theZone.onFlag then
theZone.onFlagVal = cfxZones.getFlagValue(theZone.onFlag, theZone) -- trigger.misc.getUserFlag(theZone.onFlag) -- save last value theZone.onFlagVal = theZone:getFlagValue(theZone.onFlag) -- save last value
end end
if cfxZones.hasProperty(theZone, "off?") then if theZone:hasProperty("off?") then
theZone.offFlag = cfxZones.getStringFromZoneProperty(theZone, "off?", "none") theZone.offFlag = theZone:getStringFromZoneProperty("off?", "none")
end end
if theZone.offFlag then if theZone.offFlag then
theZone.offFlagVal = cfxZones.getFlagValue(theZone.offFlag, theZone) --trigger.misc.getUserFlag(theZone.offFlag) -- save last value theZone.offFlagVal = theZone:getFlagValue(theZone.offFlag) -- save last value
end end
-- start it -- start it
@ -170,7 +171,7 @@ function cfxNDB.update()
if not theNDB.lastLoc then if not theNDB.lastLoc then
cfxNDB.startNDB(theNDB) -- never was started cfxNDB.startNDB(theNDB) -- never was started
else else
local loc = cfxZones.getPoint(theNDB) -- y === 0 local loc = theNDB:getPoint() -- y === 0
loc.y = land.getHeight({x = loc.x, y = loc.z}) -- get y from land loc.y = land.getHeight({x = loc.x, y = loc.z}) -- get y from land
local delta = dcsCommon.dist(loc, theNDB.lastLoc) local delta = dcsCommon.dist(loc, theNDB.lastLoc)
if delta > cfxNDB.maxDist then if delta > cfxNDB.maxDist then
@ -182,17 +183,15 @@ function cfxNDB.update()
end end
-- now check triggers to start/stop -- now check triggers to start/stop
if cfxZones.testZoneFlag(theNDB, theNDB.onFlag, theNDB.ndbTriggerMethod, "onFlagVal") then if theNDB:testZoneFlag(theNDB.onFlag, theNDB.ndbTriggerMethod, "onFlagVal") then
-- yupp, trigger start -- yupp, trigger start
cfxNDB.startNDB(theNDB) cfxNDB.startNDB(theNDB)
end end
if theNDB:testZoneFlag(theNDB.offFlag, theNDB.ndbTriggerMethod, "offFlagVal") then
if cfxZones.testZoneFlag(theNDB, theNDB.offFlag, theNDB.ndbTriggerMethod, "offFlagVal") then
-- yupp, trigger start -- yupp, trigger start
cfxNDB.stopNDB(theNDB) cfxNDB.stopNDB(theNDB)
end end
end end
end end
@ -205,10 +204,10 @@ function cfxNDB.readConfig()
theZone = cfxZones.createSimpleZone("ndbConfig") theZone = cfxZones.createSimpleZone("ndbConfig")
end end
cfxNDB.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false) cfxNDB.verbose = theZone.verbose
cfxNDB.ndbRefresh = cfxZones.getNumberFromZoneProperty(theZone, "ndbRefresh", 10) cfxNDB.ndbRefresh = theZone:getNumberFromZoneProperty("ndbRefresh", 10)
cfxNDB.maxDist = cfxZones.getNumberFromZoneProperty(theZone, "maxDist", 50) -- max 50m error for movement cfxNDB.maxDist = theZone:getNumberFromZoneProperty("maxDist", 50) -- max 50m error for movement
if cfxNDB.verbose then if cfxNDB.verbose then
trigger.action.outText("***ndb: read config", 30) trigger.action.outText("***ndb: read config", 30)

View File

@ -1,5 +1,5 @@
cfxObjectSpawnZones = {} cfxObjectSpawnZones = {}
cfxObjectSpawnZones.version = "1.3.1" cfxObjectSpawnZones.version = "2.0.0"
cfxObjectSpawnZones.requiredLibs = { cfxObjectSpawnZones.requiredLibs = {
"dcsCommon", -- common is of course needed for everything "dcsCommon", -- common is of course needed for everything
-- pretty stupid to check for this since we -- pretty stupid to check for this since we
@ -8,31 +8,33 @@ cfxObjectSpawnZones.requiredLibs = {
} }
cfxObjectSpawnZones.ups = 1 cfxObjectSpawnZones.ups = 1
cfxObjectSpawnZones.verbose = false cfxObjectSpawnZones.verbose = false
-- --[[--
-- Zones that conform with this requirements spawn toops automatically Zones that conform with this requirements spawn objects automatically
-- *** DOES NOT EXTEND ZONES *** *** DOES NOT EXTEND ZONES ***
--
-- version history
-- 1.0.0 - based on 1.4.6 version from cfxSpawnZones
-- 1.1.0 - uses linkedUnit, so spawnming can occur on ships
-- make sure you also enable useOffset to place the
-- statics away from the center of the ship
-- 1.1.1 - also processes paused flag
-- - despawnRemaining(spawner)
-- 1.1.2 - autoRemove option re-installed
-- - added possibility to autoUnlink
-- 1.1.3 - ME-triggered flag via f? and triggerFlag
-- 1.1.4 - activate?, pause? attributes
-- 1.1.5 - spawn?, spawnObjects? synonyms
-- 1.2.0 - DML flag upgrade
-- 1.2.1 - config zone
-- - autoLink bug (zone instead of spawner accessed)
-- 1.3.0 - better synonym handling
-- - useDelicates link to delicate when spawned
-- - spawned single and multi-objects can be made delicates
-- 1.3.1 - baseName can be set to zone's name by giving "*"
-- 1.3.2 - delicateName supports '*' to refer to own zone
version history
1.0.0 - based on 1.4.6 version from cfxSpawnZones
1.1.0 - uses linkedUnit, so spawnming can occur on ships
make sure you also enable useOffset to place the
statics away from the center of the ship
1.1.1 - also processes paused flag
- despawnRemaining(spawner)
1.1.2 - autoRemove option re-installed
- added possibility to autoUnlink
1.1.3 - ME-triggered flag via f? and triggerFlag
1.1.4 - activate?, pause? attributes
1.1.5 - spawn?, spawnObjects? synonyms
1.2.0 - DML flag upgrade
1.2.1 - config zone
- autoLink bug (zone instead of spawner accessed)
1.3.0 - better synonym handling
- useDelicates link to delicate when spawned
- spawned single and multi-objects can be made delicates
1.3.1 - baseName can be set to zone's name by giving "*"
1.3.2 - delicateName supports '*' to refer to own zone
2.0.0 - dmlZones
--]]--
-- respawn currently happens after theSpawns is deleted and cooldown seconds have passed -- respawn currently happens after theSpawns is deleted and cooldown seconds have passed
cfxObjectSpawnZones.allSpawners = {} cfxObjectSpawnZones.allSpawners = {}
@ -59,72 +61,70 @@ end
function cfxObjectSpawnZones.createSpawner(inZone) function cfxObjectSpawnZones.createSpawner(inZone)
local theSpawner = {} local theSpawner = {}
theSpawner.zone = inZone theSpawner.zone = inZone
theSpawner.name = inZone.name theSpawner.name = inZone.name -- provide compat with cfxZones (not dmlZones, though)
-- connect with ME if a trigger flag is given -- connect with ME if a trigger flag is given
if cfxZones.hasProperty(inZone, "f?") then if inZone:hasProperty("f?") then
theSpawner.triggerFlag = cfxZones.getStringFromZoneProperty(inZone, "f?", "none") theSpawner.triggerFlag = inZone:getStringFromZoneProperty("f?", "none")
elseif cfxZones.hasProperty(inZone, "spawn?") then elseif inZone:hasProperty("spawn?") then
theSpawner.triggerFlag = cfxZones.getStringFromZoneProperty(inZone, "spawn?", "none") theSpawner.triggerFlag = inZone:getStringFromZoneProperty("spawn?", "none")
elseif cfxZones.hasProperty(inZone, "spawnObjects?") then elseif inZone:hasProperty("spawnObjects?") then
theSpawner.triggerFlag = cfxZones.getStringFromZoneProperty(inZone, "spawnObjects?", "none") theSpawner.triggerFlag = inZone:getStringFromZoneProperty( "spawnObjects?", "none")
end end
if theSpawner.triggerFlag then if theSpawner.triggerFlag then
theSpawner.lastTriggerValue = cfxZones.getFlagValue(theSpawner.triggerFlag, theSpawner) -- trigger.misc.getUserFlag(theSpawner.triggerFlag) theSpawner.lastTriggerValue = cfxZones.getFlagValue(theSpawner.triggerFlag, theSpawner)
end end
if cfxZones.hasProperty(inZone, "activate?") then if inZone:hasProperty("activate?") then
theSpawner.activateFlag = cfxZones.getStringFromZoneProperty(inZone, "activate?", "none") theSpawner.activateFlag = inZone:getStringFromZoneProperty( "activate?", "none")
theSpawner.lastActivateValue = cfxZones.getFlagValue(theSpawner.activateFlag, theSpawner) --trigger.misc.getUserFlag(theSpawner.activateFlag) theSpawner.lastActivateValue = cfxZones.getFlagValue(theSpawner.activateFlag, theSpawner) --trigger.misc.getUserFlag(theSpawner.activateFlag)
end end
if cfxZones.hasProperty(inZone, "pause?") then if inZone:hasProperty("pause?") then
theSpawner.pauseFlag = cfxZones.getStringFromZoneProperty(inZone, "pause?", "none") theSpawner.pauseFlag = inZone:getStringFromZoneProperty("pause?", "none")
theSpawner.lastPauseValue = cfxZones.getFlagValue(theSpawner.lastPauseValue, theSpawner) -- trigger.misc.getUserFlag(theSpawner.pauseFlag) theSpawner.lastPauseValue = cfxZones.getFlagValue(theSpawner.lastPauseValue, theSpawner)
end end
theSpawner.types = cfxZones.getStringFromZoneProperty(inZone, "types", "White_Tyre") theSpawner.types = inZone:getStringFromZoneProperty("types", "White_Tyre")
local n = cfxZones.getNumberFromZoneProperty(inZone, "count", 1) -- DO NOT CONFUSE WITH OWN PROPERTY COUNT for unique names!!! local n = inZone:getNumberFromZoneProperty("count", 1) -- DO NOT CONFUSE WITH OWN PROPERTY COUNT for unique names!!!
if n < 1 then n = 1 end -- sanity check. if n < 1 then n = 1 end -- sanity check.
theSpawner.numObj = n theSpawner.numObj = n
theSpawner.country = cfxZones.getNumberFromZoneProperty(inZone, "country", 2) -- coalition2county(theSpawner.owner) theSpawner.country = inZone:getNumberFromZoneProperty("country", 2)
theSpawner.rawOwner = coalition.getCountryCoalition(theSpawner.country) theSpawner.rawOwner = coalition.getCountryCoalition(theSpawner.country)
theSpawner.baseName = cfxZones.getStringFromZoneProperty(inZone, "baseName", dcsCommon.uuid("objSpwn")) theSpawner.baseName = inZone:getStringFromZoneProperty("baseName", "*")
theSpawner.baseName = dcsCommon.trim(theSpawner.baseName) theSpawner.baseName = dcsCommon.trim(theSpawner.baseName)
if theSpawner.baseName == "*" then if theSpawner.baseName == "*" then
theSpawner.baseName = inZone.name -- convenience shortcut theSpawner.baseName = inZone.name -- convenience shortcut
end end
--cfxZones.getZoneProperty(inZone, "baseName") --cfxZones.getZoneProperty(inZone, "baseName")
theSpawner.cooldown = cfxZones.getNumberFromZoneProperty(inZone, "cooldown", 60) theSpawner.cooldown = inZone:getNumberFromZoneProperty("cooldown", 60)
theSpawner.lastSpawnTimeStamp = -10000 -- just init so it will always work theSpawner.lastSpawnTimeStamp = -10000 -- just init so it will always work
theSpawner.autoRemove = cfxZones.getBoolFromZoneProperty(inZone, "autoRemove", false) theSpawner.autoRemove = inZone:getBoolFromZoneProperty("autoRemove", false)
theSpawner.autoLink = cfxZones.getBoolFromZoneProperty(inZone, "autoLink", true) theSpawner.autoLink = inZone:getBoolFromZoneProperty("autoLink", true)
theSpawner.heading = cfxZones.getNumberFromZoneProperty(inZone, "heading", 0) theSpawner.heading = inZone:getNumberFromZoneProperty("heading", 0)
theSpawner.weight = cfxZones.getNumberFromZoneProperty(inZone, "weight", 0) theSpawner.weight = inZone:getNumberFromZoneProperty("weight", 0)
if theSpawner.weight < 0 then theSpawner.weight = 0 end if theSpawner.weight < 0 then theSpawner.weight = 0 end
theSpawner.isCargo = cfxZones.getBoolFromZoneProperty(inZone, "isCargo", false) theSpawner.isCargo = inZone:getBoolFromZoneProperty("isCargo", false)
if theSpawner.isCargo == true and theSpawner.weight < 100 then theSpawner.weight = 100 end if theSpawner.isCargo == true and theSpawner.weight < 100 then theSpawner.weight = 100 end
theSpawner.managed = cfxZones.getBoolFromZoneProperty(inZone, "managed", true) -- defaults to managed cargo theSpawner.managed = inZone:getBoolFromZoneProperty("managed", true) -- defaults to managed cargo
theSpawner.cdTimer = 0 -- used for cooldown. if timer.getTime < this value, don't spawn theSpawner.cdTimer = 0 -- used for cooldown. if timer.getTime < this value, don't spawn
-- theSpawner.cdStarted = false -- used to initiate cooldown when all items in theSpawns disappear
theSpawner.count = 1 -- used to create names, and count how many groups created theSpawner.count = 1 -- used to create names, and count how many groups created
theSpawner.theSpawns = {} -- all items that are spawned. re-spawn happens if they are all out theSpawner.theSpawns = {} -- all items that are spawned. re-spawn happens if they are all out
theSpawner.maxSpawns = cfxZones.getNumberFromZoneProperty(inZone, "maxSpawns", 1) theSpawner.maxSpawns = inZone:getNumberFromZoneProperty("maxSpawns", 1)
theSpawner.paused = cfxZones.getBoolFromZoneProperty(inZone, "paused", false) theSpawner.paused = inZone:getBoolFromZoneProperty("paused", false)
theSpawner.requestable = cfxZones.getBoolFromZoneProperty(inZone, "requestable", false) theSpawner.requestable = inZone:getBoolFromZoneProperty("requestable", false)
if theSpawner.requestable then theSpawner.paused = true end if theSpawner.requestable then theSpawner.paused = true end
-- see if the spawn can be made brittle/delicte -- see if the spawn can be made brittle/delicte
if cfxZones.hasProperty(inZone, "useDelicates") then if inZone:hasProperty("useDelicates") then
theSpawner.delicateName = dcsCommon.trim(cfxZones.getStringFromZoneProperty(inZone, "useDelicates", "<none>")) theSpawner.delicateName = dcsCommon.trim(inZone:getStringFromZoneProperty("useDelicates", "<none>"))
if theSpawner.delicateName == "*" then theSpawner.delicateName = inZone.name end if theSpawner.delicateName == "*" then theSpawner.delicateName = inZone.name end
end end

View File

@ -43,11 +43,7 @@ cfxOwnedZones.initialized = false
owned zones is a module that manages conquerable zones and keeps a record owned zones is a module that manages conquerable zones and keeps a record
of who owns the zone based on rules of who owns the zone based on rules
*** EXTENTDS ZONES ***
*** EXTENTDS ZONES ***, so compatible with cfxZones, pilotSafe (limited airframes), may conflict with FARPZones
owned zones are identified by the 'owner' property. It can be initially set to nothing (default), NEUTRAL, RED or BLUE
when a zone changes hands, a callback can be installed to be told of that fact when a zone changes hands, a callback can be installed to be told of that fact
callback has the format (zone, newOwner, formerOwner) with zone being the Zone, and new owner and former owners callback has the format (zone, newOwner, formerOwner) with zone being the Zone, and new owner and former owners

View File

@ -1,5 +1,5 @@
cfxZones = {} cfxZones = {}
cfxZones.version = "4.0.7" cfxZones.version = "4.0.9"
-- cf/x zone management module -- cf/x zone management module
-- reads dcs zones and makes them accessible and mutable -- reads dcs zones and makes them accessible and mutable
@ -56,7 +56,15 @@ cfxZones.version = "4.0.7"
- processDynamicValueVU - processDynamicValueVU
- 4.0.6 - hash mark forgotten QoL - 4.0.6 - hash mark forgotten QoL
- 4.0.7 - drawZone() - 4.0.7 - drawZone()
- 4.0.8 - markZoneWithObjects()
- cleanup
- markCenterWithObject
- markPointWithObject
- 4.0.9 - createPolyZone now correctly returns new zone
- createSimplePolyZone correctly passes location to createPolyZone
- createPolyZone now correctly sets zone.point
- createPolyZone now correctly inits dcsOrigin
- createCircleZone noew correctly inits dcsOrigin
--]]-- --]]--
-- --
@ -304,7 +312,7 @@ function cfxZones.calculateZoneBounds(theZone)
-- we may need to ascertain why we need ul, ur, ll, lr instead of just ll and ur -- we may need to ascertain why we need ul, ur, ll, lr instead of just ll and ur
-- store pRad -- store pRad
theZone.pRad = pRad -- not sure we'll ever need that, but at least we have it theZone.pRad = pRad -- not sure we'll ever need that, but at least we have it
-- trigger.action.outText("+++Zones: poly zone <" .. theZone.name .. "> has pRad = " .. pRad, 30) -- remember to remove me
else else
-- huston, we have a problem -- huston, we have a problem
if cfxZones.verbose then if cfxZones.verbose then
@ -320,27 +328,10 @@ end
function cfxZones.createPoint(x, y, z) -- bridge to dcsCommon, backward comp. function cfxZones.createPoint(x, y, z) -- bridge to dcsCommon, backward comp.
return dcsCommon.createPoint(x, y, z) return dcsCommon.createPoint(x, y, z)
--[[--
local newPoint = {}
newPoint.x = x
newPoint.y = y
newPoint.z = z
return newPoint --]]--
end end
function cfxZones.copyPoint(inPoint) -- bridge to dcsCommon, backward comp. function cfxZones.copyPoint(inPoint) -- bridge to dcsCommon, backward comp.
return dcsCommon.copyPoint(inPoint) return dcsCommon.copyPoint(inPoint)
--[[--
local newPoint = {}
newPoint.x = inPoint.x
newPoint.y = inPoint.y
-- handle xz only
if inPoint.z then
newPoint.z = inPoint.z
else
newPoint.z = inPoint.y
end
return newPoint --]]--
end end
function cfxZones.createHeightCorrectedPoint(inPoint) -- this should be in dcsCommon function cfxZones.createHeightCorrectedPoint(inPoint) -- this should be in dcsCommon
@ -535,6 +526,7 @@ function cfxZones.createCircleZone(name, x, z, radius)
newZone.name = name newZone.name = name
newZone.radius = radius newZone.radius = radius
newZone.point = dcsCommon.createPoint(x, 0, z) newZone.point = dcsCommon.createPoint(x, 0, z)
newZone.dcsOrigin = dcsCommon.createPoint(x, 0, z)
-- props -- props
newZone.properties = {} newZone.properties = {}
@ -552,8 +544,9 @@ function cfxZones.createSimplePolyZone(name, location, points, addToManaged)
end end
if not location.x then location.x = 0 end if not location.x then location.x = 0 end
if not location.z then location.z = 0 end if not location.z then location.z = 0 end
if not location.y then location.y = 0 end
local newZone = cfxZones.createPolyZone(name, points) local newZone = cfxZones.createPolyZone(name, points, location)
if addToManaged then if addToManaged then
cfxZones.addZoneToManagedZones(newZone) cfxZones.addZoneToManagedZones(newZone)
@ -593,8 +586,11 @@ function cfxZones.createSimpleQuadZone(name, location, points, addToManaged)
return cfxZones.createSimplePolyZone(name, location, points, addToManaged) return cfxZones.createSimplePolyZone(name, location, points, addToManaged)
end end
function cfxZones.createPolyZone(name, poly) -- poly must be array of point type function cfxZones.createPolyZone(name, poly, location) -- poly must be array of point type
local newZone = dmlZone:new(nil) -- {} OOP compatibility local newZone = dmlZone:new(nil) -- {} OOP compatibility
if not location then location = {x=0, y=0, z=0} end
newZone.point = dcsCommon.createPoint(location.x, 0, location.z)
newZone.dcsOrigin = dcsCommon.createPoint(location.x, 0, location.z)
newZone.isCircle = false newZone.isCircle = false
newZone.isPoly = true newZone.isPoly = true
newZone.poly = {} newZone.poly = {}
@ -612,10 +608,9 @@ function cfxZones.createPolyZone(name, poly) -- poly must be array of point type
newZone.properties = {} newZone.properties = {}
cfxZones.calculateZoneBounds(newZone) cfxZones.calculateZoneBounds(newZone)
return newZone
end end
function cfxZones.createRandomZoneInZone(name, inZone, targetRadius, entirelyInside) function cfxZones.createRandomZoneInZone(name, inZone, targetRadius, entirelyInside)
-- create a new circular zone with center placed inside inZone -- create a new circular zone with center placed inside inZone
-- if entirelyInside is false, only the zone's center is guaranteed to be inside -- if entirelyInside is false, only the zone's center is guaranteed to be inside
@ -641,23 +636,6 @@ function cfxZones.createRandomZoneInZone(name, inZone, targetRadius, entirelyIns
return newZone return newZone
elseif inZone.isPoly then elseif inZone.isPoly then
-- we have a poly zone. the way we do this is simple:
-- generate random x, z with ranges of the bounding box
-- until the point falls within the polygon.
--[[ replaced by new code
local newPoint = {}
local emergencyBrake = 0
repeat
newPoint = cfxZones.createRandomPointInsideBounds(inZone.bounds)
emergencyBrake = emergencyBrake + 1
if (emergencyBrake > 100) then
newPoint = cfxZones.copyPoint(inZone.Point)
trigger.action.outText("CreateZoneInZone: emergency brake for inZone" .. inZone.name, 10)
break
end
until cfxZones.isPointInsidePoly(newPoint, inZone.poly)
--]]--
local newPoint = cfxZones.createRandomPointInPolyZone(inZone) local newPoint = cfxZones.createRandomPointInPolyZone(inZone)
-- construct new zone -- construct new zone
local newZone = cfxZones.createCircleZone(name, newPoint.x, newPoint.z, targetRadius) local newZone = cfxZones.createCircleZone(name, newPoint.x, newPoint.z, targetRadius)
@ -833,7 +811,6 @@ function cfxZones.getZoneVolume(theZone)
} }
return vol return vol
elseif (theZone.isPoly) then elseif (theZone.isPoly) then
--trigger.action.outText("zne: isPointInside: " .. theZone.name .. " is Polyzone!", 30)
-- build the box volume, using the zone's bounds ll and ur points -- build the box volume, using the zone's bounds ll and ur points
local lowerLeft = {} local lowerLeft = {}
-- we build x = westerm y = southern, Z = alt -- we build x = westerm y = southern, Z = alt
@ -870,17 +847,11 @@ end
function cfxZones.declutterZone(theZone) function cfxZones.declutterZone(theZone)
if not theZone then return end if not theZone then return end
local theVol = cfxZones.getZoneVolume(theZone) local theVol = cfxZones.getZoneVolume(theZone)
-- if theZone.verbose then
-- dcsCommon.dumpVar2Str("vol", theVol)
-- end
world.removeJunk(theVol) world.removeJunk(theVol)
end end
function dmlZone:declutterZone() function dmlZone:declutterZone()
local theVol = cfxZones.getZoneVolume(self) local theVol = cfxZones.getZoneVolume(self)
-- if self.verbose then
-- dcsCommon.dumpVar2Str("vol", theVol)
-- end
world.removeJunk(theVol) world.removeJunk(theVol)
end end
@ -888,8 +859,7 @@ end
-- units / groups in zone -- units / groups in zone
-- --
function cfxZones.allGroupsInZone(theZone, categ) -- categ is optional, must be code function cfxZones.allGroupsInZone(theZone, categ) -- categ is optional, must be code
-- warning: does not check for exiting! -- warning: does not check for existing!
--trigger.action.outText("Zone " .. theZone.name .. " radius " .. theZone.radius, 30)
local inZones = {} local inZones = {}
local coals = {0, 1, 2} -- all coalitions local coals = {0, 1, 2} -- all coalitions
for idx, coa in pairs(coals) do for idx, coa in pairs(coals) do
@ -908,8 +878,7 @@ function dmlZone:allGroupsInZone(categ)
end end
function cfxZones.allGroupNamesInZone(theZone, categ) -- categ is optional, must be code function cfxZones.allGroupNamesInZone(theZone, categ) -- categ is optional, must be code
-- warning: does not check for exiting! -- warning: does not check for existing!
--trigger.action.outText("Zone " .. theZone.name .. " radius " .. theZone.radius, 30)
local inZones = {} local inZones = {}
local coals = {0, 1, 2} -- all coalitions local coals = {0, 1, 2} -- all coalitions
for idx, coa in pairs(coals) do for idx, coa in pairs(coals) do
@ -928,7 +897,7 @@ function dmlZone:allGroupNamesInZone(categ)
end end
function cfxZones.allStaticsInZone(theZone, useOrigin) -- categ is optional, must be code function cfxZones.allStaticsInZone(theZone, useOrigin) -- categ is optional, must be code
-- warning: does not check for exiting! -- warning: does not check for existing!
local inZones = {} local inZones = {}
local coals = {0, 1, 2} -- all coalitions local coals = {0, 1, 2} -- all coalitions
for idx, coa in pairs(coals) do for idx, coa in pairs(coals) do
@ -976,11 +945,9 @@ function cfxZones.isGroupPartiallyInZone(aGroup, aZone)
if aUnit:isExist() and aUnit:getLife() > 1 then if aUnit:isExist() and aUnit:getLife() > 1 then
local p = aUnit:getPoint() local p = aUnit:getPoint()
local inzone, percent, dist = cfxZones.pointInZone(p, aZone) local inzone, percent, dist = cfxZones.pointInZone(p, aZone)
if inzone then -- cfxZones.isPointInsideZone(p, aZone) then if inzone then
--trigger.action.outText("zne: YAY <" .. aUnit:getName() .. "> IS IN " .. aZone.name, 30)
return true return true
end end
--trigger.action.outText("zne: <" .. aUnit:getName() .. "> not in " .. aZone.name .. ", dist = " .. dist .. ", rad = ", aZone.radius, 30)
end end
end end
return false return false
@ -1073,11 +1040,7 @@ function cfxZones.dumpZones(zoneTable)
end end
trigger.action.outText("Zones end", 10) trigger.action.outText("Zones end", 10)
end end
--[[-- moved to dcsCommon
function cfxZones.stringStartsWith(theString, thePrefix)
return theString:find(thePrefix) == 1
end
--]]--
function cfxZones.keysForTable(theTable) function cfxZones.keysForTable(theTable)
local keyset={} local keyset={}
local n=0 local n=0
@ -1113,17 +1076,12 @@ end
-- return all zones from the zone table that begin with string prefix -- return all zones from the zone table that begin with string prefix
-- --
function cfxZones.zonesStartingWithName(prefix, searchSet) function cfxZones.zonesStartingWithName(prefix, searchSet)
if not searchSet then searchSet = cfxZones.zones end if not searchSet then searchSet = cfxZones.zones end
-- trigger.action.outText("Enter: zonesStartingWithName for " .. prefix , 30)
local prefixZones = {} local prefixZones = {}
prefix = prefix:upper() -- all zones have UPPERCASE NAMES! THEY SCREAM AT YOU prefix = prefix:upper() -- all zones have UPPERCASE NAMES! THEY SCREAM AT YOU
for name, zone in pairs(searchSet) do for name, zone in pairs(searchSet) do
-- trigger.action.outText("testing " .. name:upper() .. " starts with " .. prefix , 30)
if dcsCommon.stringStartsWith(name:upper(), prefix) then if dcsCommon.stringStartsWith(name:upper(), prefix) then
prefixZones[name] = zone -- note: ref copy! prefixZones[name] = zone -- note: ref copy!
--trigger.action.outText("zone with prefix <" .. prefix .. "> found: " .. name, 10)
end end
end end
@ -1396,19 +1354,6 @@ function dmlZone:closestUnitToZoneCenter(theUnits)
return cfxZones.closestUnitToZoneCenter(theUnits, self) return cfxZones.closestUnitToZoneCenter(theUnits, self)
end end
--[[
function cfxZones.anyPlayerInZone(theZone) -- returns first player it finds
for pname, pinfo in pairs(cfxPlayer.playerDB) do
local playerUnit = pinfo.unit
if (cfxZones.unitInZone(playerUnit, theZone)) then
return true, playerUnit
end
end -- for all players
return false, nil
end
--]]--
-- grow zone -- grow zone
function cfxZones.growZone() function cfxZones.growZone()
-- circular zones simply increase radius -- circular zones simply increase radius
@ -1776,14 +1721,6 @@ end
function dmlZone:getFlagValue(theFlag) function dmlZone:getFlagValue(theFlag)
return cfxZones.getFlagValue(theFlag, self) return cfxZones.getFlagValue(theFlag, self)
end end
--[[--
function cfxZones.isMEFlag(inFlag)
-- do NOT use me
trigger.action.outText("+++zne: warning: deprecated isMEFlag", 30)
return true
-- returns true if inFlag is a pure positive number
end
--]]--
function cfxZones.verifyMethod(theMethod, theZone) function cfxZones.verifyMethod(theMethod, theZone)
local lMethod = string.lower(theMethod) local lMethod = string.lower(theMethod)
@ -2082,7 +2019,6 @@ function cfxZones.testZoneFlag(theZone, theFlagName, theMethod, latchName)
return false, currVal return false, currVal
end end
--trigger.action.outText("+++Zne: about to test: c = " .. currVal .. ", l = " .. lastVal, 30)
local testResult = cfxZones.testFlagByMethodForZone(currVal, lastVal, theMethod, theZone) local testResult = cfxZones.testFlagByMethodForZone(currVal, lastVal, theMethod, theZone)
-- update latch by method -- update latch by method
@ -2197,12 +2133,10 @@ end
function cfxZones.getZoneProperty(cZone, theKey) function cfxZones.getZoneProperty(cZone, theKey)
if not cZone then if not cZone then
trigger.action.outText("+++zone: no zone in getZoneProperty", 30) trigger.action.outText("+++zone: no zone in getZoneProperty", 30)
-- breek.here.noew = 1
return nil return nil
end end
if not theKey then if not theKey then
trigger.action.outText("+++zone: no property key in getZoneProperty for zone " .. cZone.name, 30) trigger.action.outText("+++zone: no property key in getZoneProperty for zone " .. cZone.name, 30)
-- breakme.here = 1
return return
end end
@ -2224,7 +2158,6 @@ end
function cfxZones.getStringFromZoneProperty(theZone, theProperty, default) function cfxZones.getStringFromZoneProperty(theZone, theProperty, default)
if not default then default = "" end if not default then default = "" end
-- local p = cfxZones.getZoneProperty(theZone, theProperty)
-- OOP heavy duty test here -- OOP heavy duty test here
local p = theZone:getZoneProperty(theProperty) local p = theZone:getZoneProperty(theProperty)
if not p then return default end if not p then return default end
@ -2378,7 +2311,6 @@ function cfxZones.hasProperty(theZone, theProperty)
return false return false
end end
return true return true
-- return foundIt ~= nil
end end
function dmlZone:hasProperty(theProperty) function dmlZone:hasProperty(theProperty)
@ -2539,30 +2471,22 @@ function dmlZone:getCoalitionFromZoneProperty(theProperty, default)
end end
function cfxZones.getNumberFromZoneProperty(theZone, theProperty, default) function cfxZones.getNumberFromZoneProperty(theZone, theProperty, default)
--TODO: trim string
if not default then default = 0 end if not default then default = 0 end
default = tonumber(default) default = tonumber(default)
if not default then default = 0 end -- enforce default numbner as well if not default then default = 0 end -- enforce default numbner as well
local p = cfxZones.getZoneProperty(theZone, theProperty) local p = cfxZones.getZoneProperty(theZone, theProperty)
p = tonumber(p) p = tonumber(p)
if not p then p = default end if not p then p = default end
-- if theZone.verbose then
-- trigger.action.outText("+++zne: getNumberFromZoneProperty returns <" .. p .. "> for prop <" .. theProperty .. "> in zone <" .. theZone.name .. ">", 30)
-- end
return p return p
end end
function dmlZone:getNumberFromZoneProperty(theProperty, default) function dmlZone:getNumberFromZoneProperty(theProperty, default)
--TODO: trim string
if not default then default = 0 end if not default then default = 0 end
default = tonumber(default) default = tonumber(default)
if not default then default = 0 end -- enforce default numbner as well if not default then default = 0 end -- enforce default numbner as well
local p = self:getZoneProperty(theProperty) local p = self:getZoneProperty(theProperty)
p = tonumber(p) p = tonumber(p)
if not p then p = default end if not p then p = default end
-- if self.verbose then
-- trigger.action.outText("+++zne OOP: getNumberFromZoneProperty returns <" .. p .. "> for prop <" .. theProperty .. "> in zone <" .. self.name .. ">", 30)
-- end
return p return p
end end
@ -2883,7 +2807,6 @@ function cfxZones.processDynamicValues(inMsg, theZone, msgResponses)
repeat -- iterate all patterns one by one repeat -- iterate all patterns one by one
local startLoc, endLoc = string.find(outMsg, pattern) local startLoc, endLoc = string.find(outMsg, pattern)
if startLoc then if startLoc then
--trigger.action.outText("response: found an occurence", 30)
local theValParam = string.sub(outMsg, startLoc, endLoc) local theValParam = string.sub(outMsg, startLoc, endLoc)
-- strip lead and trailer -- strip lead and trailer
local param = string.gsub(theValParam, "<rsp:%s*", "") local param = string.gsub(theValParam, "<rsp:%s*", "")
@ -3131,7 +3054,6 @@ function cfxZones.processDynamicAB(inMsg, locale)
left = dcsCommon.trim(left) left = dcsCommon.trim(left)
right = string.sub(rp, rightA+1, rightB-1) right = string.sub(rp, rightA+1, rightB-1)
right = dcsCommon.trim(right) right = dcsCommon.trim(right)
-- trigger.action.outText("+++replacer pattern <" .. rp .. "> found, val = <" .. val .. ">, A = <" .. left .. ">, B = <" .. right .. ">", 30)
local yesno = false local yesno = false
-- see if unit exists -- see if unit exists
local theUnit = Unit.getByName(val) local theUnit = Unit.getByName(val)
@ -3336,7 +3258,6 @@ function cfxZones.linkUnitToZone(theUnit, theZone, dx, dy) -- note: dy is really
-- direction to zone -- direction to zone
theZone.uHdg = unitHeading -- original unit heading to turn other theZone.uHdg = unitHeading -- original unit heading to turn other
-- units if need be -- units if need be
--trigger.action.outText("Link setup: dx=<" .. dx .. ">, dy=<" .. dy .. "> unit original hdg = <" .. math.floor(57.2958 * unitHeading) .. ">", 30)
end end
function dmlZone:linkUnitToZone(theUnit, dx, dy) -- note: dy is really Z, don't get confused!!!! function dmlZone:linkUnitToZone(theUnit, dx, dy) -- note: dy is really Z, don't get confused!!!!
@ -3386,8 +3307,6 @@ function cfxZones.calcHeadingOffset(aZone, theUnit)
-- in DCS, positive x is north (wtf?) and positive z is east -- in DCS, positive x is north (wtf?) and positive z is east
local dy = (-aZone.rxy) * math.sin(zoneBearing) local dy = (-aZone.rxy) * math.sin(zoneBearing)
local dx = aZone.rxy * math.cos(zoneBearing) local dx = aZone.rxy * math.cos(zoneBearing)
--trigger.action.outText("zone bearing is " .. math.floor(zoneBearing * 57.2958) .. " dx = <" .. dx .. "> , dy = <" .. dy .. ">", 30)
return dx, -dy -- note: dy is z coord!!!! return dx, -dy -- note: dy is z coord!!!!
end end
@ -3542,8 +3461,101 @@ function cfxZones.startMovingZones()
end end
end end
--
-- marking zones
--
function cfxZones.spreadNObjectsOverLine(theZone, n, objType, left, right, cty) -- leaves last position free
trigger.action.outText("left = " .. dcsCommon.point2text(left) .. ", right = " .. dcsCommon.point2text(right),30)
local a = {x=left.x, y=left.z}
local b = {x=right.x, y=right.z}
local dir = dcsCommon.vSub(b,a) -- vector from left to right
local dirInc = dcsCommon.vMultScalar(dir, 1/n)
local count = 0
local p = {x=left.x, y = left.z}
local baseName = dcsCommon.uuid(theZone.name)
while count < n do
local theStaticData = dcsCommon.createStaticObjectData(dcsCommon.uuid(theZone.name), objType)
dcsCommon.moveStaticDataTo(theStaticData, p.x, p.y)
local theObject = coalition.addStaticObject(cty, theStaticData)
p = dcsCommon.vAdd(p, dirInc)
count = count + 1
end
end
function cfxZones.markZoneWithObjects(theZone, objType, qtrNum, markCenter, cty) -- returns set
if not objType then objType = "Black_Tyre_RF" end
if not qtrNum then qtrNum = 3 end -- +1 for number of marks per quarter
if not cty then cty = dcsCommon.getACountryForCoalition(0) end -- some neutral county
local p = theZone:getPoint()
local newObjects = {}
if theZone.isPoly then
-- we place 4 * (qtrnum + 1) objects around the edge of the zone
-- we mark each poly along v-->v+1, placing ip and qtrNum additional points
local o = cfxZones.spreadNObjectsOverLine(theZone, qtrNum + 1, objType, theZone.poly[1], theZone.poly[2], cty)
local p = cfxZones.spreadNObjectsOverLine(theZone, qtrNum + 1, objType, theZone.poly[2], theZone.poly[3], cty)
local q = cfxZones.spreadNObjectsOverLine(theZone, qtrNum + 1, objType, theZone.poly[3], theZone.poly[4], cty)
local r = cfxZones.spreadNObjectsOverLine(theZone, qtrNum + 1, objType, theZone.poly[4], theZone.poly[1], cty)
o = dcsCommon.combineTables(o,p)
p = dcsCommon.combineTables(q,r)
newObjects = dcsCommon.combineTables(o,p)
else
local numObjects = (qtrNum + 1) * 4
local degrees = 3.14157 / 180
local degreeIncrement = (360 / numObjects) * degrees
local currDegree = 0
local radius = theZone.radius
for i=1, numObjects do
local ox = p.x + math.cos(currDegree) * radius
local oy = p.z + math.sin(currDegree) * radius -- note: z!
local theStaticData = dcsCommon.createStaticObjectData(dcsCommon.uuid(theZone.name), objType)
dcsCommon.moveStaticDataTo(theStaticData, ox, oy)
local theObject = coalition.addStaticObject(cty, theStaticData)
table.insert(newObjects, theObject)
currDegree = currDegree + degreeIncrement
end
end
if markCenter then
-- also mark the center
local theObject = cfxZones.markPointWithObject(p, objType, cty)
table.insert(newObjects, theObject)
end
return newObjects
end
function dmlZone:markZoneWithObjects(objType, qtrNum, markCenter, cty) -- returns set
return cfxZones.markZoneWithObjects(self, objType, qtrNum, markCenter)
end
function cfxZones.markCenterWithObject(theZone, objType, cty) -- returns object
local p = cfxZones.getPoint(theZone)
local theObject = cfxZones.markPointWithObject(theZone, p, objType, cty)
return theObject
end
function dmlZone:markCenterWithObject(objType, cty) -- returns object
return cfxZones.markCenterWithObject(self, objType, cty)
end
function cfxZones.markPointWithObject(theZone, p, theType, cty) -- returns object
if not cty then cty = dcsCommon.getACountryForCoalition(0) end
local ox = p.x
local oy = p.y
if p.z then oy = p.z end -- support vec 2 and vec 3
local theStaticData = dcsCommon.createStaticObjectData(dcsCommon.uuid(theZone.name), theType)
dcsCommon.moveStaticDataTo(theStaticData, ox, oy)
local theObject = coalition.addStaticObject(cty, theStaticData)
return theObject
end
function dmlZone:markPointWithObject(p, theType, cty) -- returns object
return cfxZones.markPointWithObject(self, p, theType, cty)
end
-- --
-- =========== -- ===========
-- INIT MODULE -- INIT MODULE

View File

@ -98,10 +98,10 @@ function civAir.readConfigZone()
civAir.aircraftTypes = typeArray civAir.aircraftTypes = typeArray
end end
if theZone:hasProperty("ups") then -- if theZone:hasProperty("ups") then
civAir.ups = theZone:getNumberFromZoneProperty("ups", 0.05) civAir.ups = theZone:getNumberFromZoneProperty("ups", 0.05)
if civAir.ups < .0001 then civAir.ups = 0.05 end if civAir.ups < .0001 then civAir.ups = 0.05 end
end -- end
if theZone:hasProperty("maxTraffic") then if theZone:hasProperty("maxTraffic") then
civAir.maxTraffic = theZone:getNumberFromZoneProperty( "maxTraffic", 10) civAir.maxTraffic = theZone:getNumberFromZoneProperty( "maxTraffic", 10)
@ -109,15 +109,15 @@ function civAir.readConfigZone()
civAir.maxTraffic = theZone:getNumberFromZoneProperty( "maxFlights", 10) civAir.maxTraffic = theZone:getNumberFromZoneProperty( "maxFlights", 10)
end end
if theZone:hasProperty("maxIdle") then -- if theZone:hasProperty("maxIdle") then
civAir.maxIdle = theZone:getNumberFromZoneProperty("maxIdle", 8 * 60) civAir.maxIdle = theZone:getNumberFromZoneProperty("maxIdle", 8 * 60)
end -- end
if theZone:hasProperty("initialAirSpawns") then -- if theZone:hasProperty("initialAirSpawns") then
civAir.initialAirSpawns = theZone:getBoolFromZoneProperty( "initialAirSpawns", true) civAir.initialAirSpawns = theZone:getBoolFromZoneProperty( "initialAirSpawns", true)
end -- end
civAir.verbose = theZone.verbose -- cfxZones.getBoolFromZoneProperty(theZone, "verbose", false) civAir.verbose = theZone.verbose
end end
function civAir.processZone(theZone) function civAir.processZone(theZone)

View File

@ -1,5 +1,5 @@
csarManager = {} csarManager = {}
csarManager.version = "2.2.6" csarManager.version = "2.3.1"
csarManager.verbose = false csarManager.verbose = false
csarManager.ups = 1 csarManager.ups = 1
@ -65,6 +65,14 @@ csarManager.ups = 1
- 2.2.5 - manual freq for CSAR was off by a factor of 10 - Corrected - 2.2.5 - manual freq for CSAR was off by a factor of 10 - Corrected
- 2.2.6 - useFlare, now also launches a flare in addition to smoke - 2.2.6 - useFlare, now also launches a flare in addition to smoke
- zone testing uses getPoint for zones, supports moving csar zones - zone testing uses getPoint for zones, supports moving csar zones
- 2.3.0 - dmlZones
- onRoad attribute for CSAR mission Zones
- rndLoc support
- triggerMethod support
- 2.3.1 - addPrefix option
- delay asynch OK (message only)
- offset zone on randomized soldier
- smokeDist
INTEGRATES AUTOMATICALLY WITH playerScore IF INSTALLED INTEGRATES AUTOMATICALLY WITH playerScore IF INSTALLED
@ -133,6 +141,9 @@ function csarManager.createDownedPilot(theMission, existingUnit)
-- a radius of <1, this is only for ejecting pilots -- a radius of <1, this is only for ejecting pilots
if newTargetZone.radius > 1 then if newTargetZone.radius > 1 then
aLocation, aHeading = dcsCommon.randomPointOnPerimeter(newTargetZone.radius / 2 + 3, newTargetZone.point.x, newTargetZone.point.z) aLocation, aHeading = dcsCommon.randomPointOnPerimeter(newTargetZone.radius / 2 + 3, newTargetZone.point.x, newTargetZone.point.z)
-- we now move the entire zone so it centers on unit
newTargetZone.point.x = aLocation.x
newTargetZone.point.z = aLocation.z
else else
aLocation.x = newTargetZone.point.x aLocation.x = newTargetZone.point.x
aLocation.z = newTargetZone.point.z aLocation.z = newTargetZone.point.z
@ -188,8 +199,10 @@ function csarManager.createCSARMissionData(point, theSide, freq, name, numCrew,
end end
end end
if not inRadius then inRadius = csarManager.rescueRadius end if not inRadius then inRadius = csarManager.rescueRadius end
newMission.name = name .. "-" .. csarManager.missionID -- make it uuid-capable
newMission.name = "downed " .. name .. "-" .. csarManager.missionID -- make it uuid-capable if csarManager.addPrefix then
newMission.name = "downed " .. newMission.name
end
newMission.zone = cfxZones.createSimpleZone(newMission.name, point, inRadius) --csarManager.rescueRadius) newMission.zone = cfxZones.createSimpleZone(newMission.name, point, inRadius) --csarManager.rescueRadius)
newMission.marker = mapMarker -- so it can be removed later newMission.marker = mapMarker -- so it can be removed later
newMission.isHot = false -- creating adversaries will make it hot, or when units are near. maybe implement a search later? newMission.isHot = false -- creating adversaries will make it hot, or when units are near. maybe implement a search later?
@ -468,15 +481,11 @@ function csarManager.heloLanded(theUnit)
"Evacuees", "Evacuees",
theMassObject) theMassObject)
msn = theMassObject.ref msn = theMassObject.ref
-- csarManager.successMission(myName, base.name, msn)
-- to be done when we remove troopsOnBoard
end end
-- reset weight -- reset weight
local totalMass = cargoSuper.calculateTotalMassFor(myName) local totalMass = cargoSuper.calculateTotalMassFor(myName)
trigger.action.setUnitInternalCargo(myName, totalMass) -- super recalcs trigger.action.setUnitInternalCargo(myName, totalMass) -- super recalcs
-- trigger.action.outText("+++csar: delivered - set internal weight for " .. myName .. " to " .. totalMass, 30)
-- trigger.action.setUnitInternalCargo(myName, 10) -- 10 kg as empty
conf.troopsOnBoard = {} -- empty out troops on board conf.troopsOnBoard = {} -- empty out troops on board
-- we do *not* return so we can pick up troops on -- we do *not* return so we can pick up troops on
-- a CSARBASE if they were dropped there -- a CSARBASE if they were dropped there
@ -521,6 +530,12 @@ function csarManager.heloLanded(theUnit)
30) 30)
didPickup = true; didPickup = true;
local args = {}
args.theName = theMission.name
args.mySide = mySide
args.unitName = myName
timer.scheduleFunction(csarManager.asynchSuccess, args, timer.getTime() + 3)
csarManager.removeMission(theMission) csarManager.removeMission(theMission)
table.insert(conf.troopsOnBoard, theMission) table.insert(conf.troopsOnBoard, theMission)
theMission.group:destroy() -- will shut up radio as well theMission.group:destroy() -- will shut up radio as well
@ -536,16 +551,24 @@ function csarManager.heloLanded(theUnit)
theMassObject) theMassObject)
end end
if didPickup then if didPickup then
trigger.action.outSoundForCoalition(mySide, csarManager.actionSound) -- "Quest Snare 3.wav") local args = {}
args.mySide = mySide
timer.scheduleFunction(csarManager.asynchSound, args, timer.getTime() + 3)
end end
-- reset unit's weight based on people on board -- reset unit's weight based on people on board
local totalMass = cargoSuper.calculateTotalMassFor(myName) local totalMass = cargoSuper.calculateTotalMassFor(myName)
-- WAS: trigger.action.setUnitInternalCargo(myName, 10 + #conf.troopsOnBoard * csarManager.pilotWeight) -- 10 kg as empty + per-unit time people
trigger.action.setUnitInternalCargo(myName, totalMass) -- 10 kg as empty + per-unit time people trigger.action.setUnitInternalCargo(myName, totalMass) -- 10 kg as empty + per-unit time people
-- trigger.action.outText("+++csar: set internal weight for " .. myName .. " to " .. totalMass, 30)
end end
function csarManager.asynchSuccess(args)
-- currently, we always say "OK", will check for fail later
trigger.action.outTextForCoalition(args.mySide, args.unitName .. " has loaded " .. args.theName .. "!", 30)
end
function csarManager.asynchSound(args)
trigger.action.outSoundForCoalition(args.mySide, csarManager.actionSound)
end
-- --
-- --
-- Helo took off -- Helo took off
@ -747,7 +770,6 @@ function csarManager.doListCSARRequests(args)
local point = theUnit:getPoint() local point = theUnit:getPoint()
local theSide = theUnit:getCoalition() local theSide = theUnit:getCoalition()
--trigger.action.outText("+++csar: ".. theUnit:getName() .." issued csar status request", 30)
local report = "\nCrews requesting evacuation\n" local report = "\nCrews requesting evacuation\n"
local openMissions = csarManager.openMissionsForSide(theSide) local openMissions = csarManager.openMissionsForSide(theSide)
@ -772,11 +794,10 @@ function csarManager.doListCSARRequests(args)
if #myBases < 1 then if #myBases < 1 then
report = report .. "\n\nWARNING: NO CSAR BASES TO DELIVER EVACUEES TO" report = report .. "\n\nWARNING: NO CSAR BASES TO DELIVER EVACUEES TO"
end end
report = report .. "\n" report = report .. "\n"
trigger.action.outTextForGroup(conf.id, report, 30) trigger.action.outTextForGroup(conf.id, report, 30)
trigger.action.outSoundForGroup(conf.id, csarManager.actionSound) -- "Quest Snare 3.wav") trigger.action.outSoundForGroup(conf.id, csarManager.actionSound)
end end
function csarManager.redirectStatusCarrying(args) function csarManager.redirectStatusCarrying(args)
@ -788,8 +809,6 @@ function csarManager.doStatusCarrying(args)
local param = args[2] local param = args[2]
local theUnit = conf.unit local theUnit = conf.unit
--trigger.action.outText("+++csar: ".. theUnit:getName() .." wants to know how their rescued troops are doing", 30)
-- build status report -- build status report
local report = "\nCrew Rescue Status:\n" local report = "\nCrew Rescue Status:\n"
if #conf.troopsOnBoard < 1 then if #conf.troopsOnBoard < 1 then
@ -809,7 +828,7 @@ function csarManager.doStatusCarrying(args)
report = report .. "\n" report = report .. "\n"
trigger.action.outTextForGroup(conf.id, report, 30) trigger.action.outTextForGroup(conf.id, report, 30)
trigger.action.outSoundForGroup(conf.id, csarManager.actionSound) -- "Quest Snare 3.wav") trigger.action.outSoundForGroup(conf.id, csarManager.actionSound)
end end
function csarManager.redirectUnloadOne(args) function csarManager.redirectUnloadOne(args)
@ -825,7 +844,7 @@ function csarManager.unloadOne(args)
local report = "NYI: unload one" local report = "NYI: unload one"
if theUnit:inAir() then if theUnit:inAir() then
report = "STRONGLY recommend we land first, Sir!" report = "STRONGLY recommend we land first, sir!"
trigger.action.outTextForGroup(conf.id, report, 30) trigger.action.outTextForGroup(conf.id, report, 30)
trigger.action.outSoundForGroup(conf.id, csarManager.actionSound) -- "Quest Snare 3.wav") trigger.action.outSoundForGroup(conf.id, csarManager.actionSound) -- "Quest Snare 3.wav")
return return
@ -961,22 +980,16 @@ end
function csarManager.addCSARBase(aZone) function csarManager.addCSARBase(aZone)
local csarBase = {} local csarBase = {}
csarBase.zone = aZone csarBase.zone = aZone
-- CSARBASE carries the coalition in the CSARBASE attribute
--[[-- csarBase.side = aZone:getCoalitionFromZoneProperty("CSARBASE", 0)
local bName = cfxZones.getStringFromZoneProperty(aZone, "CSARBASE", "XXX")
if bName == "XXX" then bName = aZone.name end
csarBase.name = cfxZones.getStringFromZoneProperty(aZone, "name", bName)
--]]--
-- CSARBASE now carries the coalition in the CSARBASE attribute
csarBase.side = cfxZones.getCoalitionFromZoneProperty(aZone, "CSARBASE", 0)
-- backward-compatibility to older versions. -- backward-compatibility to older versions.
-- will be deprecated -- will be deprecated
if cfxZones.hasProperty(aZone, "coalition") then if aZone:hasProperty("coalition") then
csarBase.side = cfxZones.getCoalitionFromZoneProperty(aZone, "CSARBASE", 0) csarBase.side = aZone:getCoalitionFromZoneProperty("CSARBASE", 0)
end end
-- see if we have provided a name field, default zone name -- see if we have provided a name field, default zone name
csarBase.name = cfxZones.getStringFromZoneProperty(aZone, "name", aZone.name) csarBase.name = aZone:getStringFromZoneProperty("name", aZone.name)
table.insert(csarManager.csarBases, csarBase) table.insert(csarManager.csarBases, csarBase)
@ -1003,12 +1016,11 @@ function csarManager.getCSARBasesForSide(theSide)
end end
return bases return bases
end end
--
-- --
-- U P D A T E -- U P D A T E
-- =========== -- ===========
-- --
--
-- --
-- updateCSARMissions: make sure evacuees are still alive -- updateCSARMissions: make sure evacuees are still alive
@ -1106,7 +1118,7 @@ function csarManager.update() -- every second
-- also pop smoke if not popped already, or more than 5 minutes ago -- also pop smoke if not popped already, or more than 5 minutes ago
if csarManager.useSmoke and (timer.getTime() - csarMission.lastSmokeTime) >= 5 * 60 then if csarManager.useSmoke and (timer.getTime() - csarMission.lastSmokeTime) >= 5 * 60 then
local smokePoint = dcsCommon.randomPointOnPerimeter( local smokePoint = dcsCommon.randomPointOnPerimeter(
50, csarMission.zone.point.x, csarMission.zone.point.z) --cfxZones.createHeightCorrectedPoint(csarMission.zone.point) csarManager.smokeDist, csarMission.zone.point.x, csarMission.zone.point.z) --cfxZones.createHeightCorrectedPoint(csarMission.zone.point)
-- trigger.action.smoke(smokePoint, 4 ) -- trigger.action.smoke(smokePoint, 4 )
dcsCommon.markPointWithSmoke(smokePoint, csarManager.smokeColor) dcsCommon.markPointWithSmoke(smokePoint, csarManager.smokeColor)
csarMission.lastSmokeTime = timer.getTime() csarMission.lastSmokeTime = timer.getTime()
@ -1120,11 +1132,11 @@ function csarManager.update() -- every second
local ep = evacuee:getPoint() local ep = evacuee:getPoint()
d = dcsCommon.distFlat(uPoint, ep) d = dcsCommon.distFlat(uPoint, ep)
d = math.floor(d * 10) / 10 d = math.floor(d * 10) / 10
if d < csarManager.hoverRadius * 2 then if d < csarManager.rescueTriggerRange * 0.5 then --csarManager.hoverRadius * 2 then
local ownHeading = dcsCommon.getUnitHeadingDegrees(aUnit) local ownHeading = dcsCommon.getUnitHeadingDegrees(aUnit)
local oclock = dcsCommon.clockPositionOfARelativeToB(ep, uPoint, ownHeading) .. " o'clock" local oclock = dcsCommon.clockPositionOfARelativeToB(ep, uPoint, ownHeading) .. " o'clock"
-- log distance -- log distance
local hoverMsg = "Closing on " .. csarMission.name .. ", " .. d * 3 .. "ft on your " .. oclock .. " o'clock" local hoverMsg = "Closing on " .. csarMission.name .. ", " .. d * 1 .. "m on your " .. oclock .. " o'clock"
if d < csarManager.hoverRadius then if d < csarManager.hoverRadius then
if (agl <= csarManager.hoverAlt) and (agl > 3) then if (agl <= csarManager.hoverAlt) and (agl > 3) then
@ -1137,7 +1149,7 @@ function csarManager.update() -- every second
hoverTime = timer.getTime() - hoverTime -- calculate number of seconds hoverTime = timer.getTime() - hoverTime -- calculate number of seconds
local remainder = math.floor(csarManager.hoverDuration - hoverTime) local remainder = math.floor(csarManager.hoverDuration - hoverTime)
if remainder < 1 then remainder = 1 end if remainder < 1 then remainder = 1 end
hoverMsg = "Steady... " .. d * 3 .. "ft to your " .. oclock .. " o'clock, winching... (" .. remainder .. ")" hoverMsg = "Steady... " .. d * 1 .. "m to your " .. oclock .. " o'clock, winching... (" .. remainder .. ")"
if hoverTime > csarManager.hoverDuration then if hoverTime > csarManager.hoverDuration then
-- we rescued the guy! -- we rescued the guy!
hoverMsg = "We have " .. csarMission.name .. " safely on board!" hoverMsg = "We have " .. csarMission.name .. " safely on board!"
@ -1172,7 +1184,7 @@ function csarManager.update() -- every second
trigger.action.outTextForGroup(uID, hoverMsg, 30, true) trigger.action.outTextForGroup(uID, hoverMsg, 30, true)
return -- only ever one winch op return -- only ever one winch op
else -- too high for hover else -- too high for hover
hoverMsg = "Evacuee " .. d * 3 .. "ft on your " .. oclock .. " o'clock; land or descend to between 10 and 90 AGL for winching" hoverMsg = "Evacuee " .. d * 1 .. "m on your " .. oclock .. " o'clock; land or descend to between 10 and 90 AGL for winching"
csarMission.hoveringUnits[uName] = nil -- reset timer csarMission.hoveringUnits[uName] = nil -- reset timer
end end
else -- not inside hover dist else -- not inside hover dist
@ -1199,12 +1211,17 @@ function csarManager.update() -- every second
-- check if their flag value has changed -- check if their flag value has changed
if theZone.startCSAR then if theZone.startCSAR then
-- this should always be true, but you never know -- this should always be true, but you never know
local currVal = cfxZones.getFlagValue(theZone.startCSAR, theZone) -- local currVal = theZone:getFlagValue(theZone.startCSAR)
if currVal ~= theZone.lastCSARVal then -- if currVal ~= theZone.lastCSARVal then
if theZone:testZoneFlag(theZone.startCSAR, theZone.triggerMethod, "lastCSARVal") then
-- set up random point in zone -- set up random point in zone
local mPoint = cfxZones.createRandomPointInZone(theZone) local mPoint = theZone:getPoint()
if theZone.rndLoc then mPoint = theZone:createRandomPointInZone() end
if theZone.onRoad then
mPoint.x, mPoint.z = land.getClosestPointOnRoads('roads',mPoint.x, mPoint.z)
end
local theMission = csarManager.createCSARMissionData( local theMission = csarManager.createCSARMissionData(
mPoint, --cfxZones.getPoint(theZone), -- point mPoint,
theZone.csarSide, -- theSide theZone.csarSide, -- theSide
theZone.csarFreq, -- freq theZone.csarFreq, -- freq
theZone.csarName, -- name theZone.csarName, -- name
@ -1214,7 +1231,7 @@ function csarManager.update() -- every second
0.1, --theZone.radius) -- radius 0.1, --theZone.radius) -- radius
nil) -- parashoo unit nil) -- parashoo unit
csarManager.addMission(theMission) csarManager.addMission(theMission)
theZone.lastCSARVal = currVal --theZone.lastCSARVal = currVal
if csarManager.verbose then if csarManager.verbose then
trigger.action.outText("+++csar: started CSAR mission " .. theZone.csarName, 30) trigger.action.outText("+++csar: started CSAR mission " .. theZone.csarName, 30)
end end
@ -1236,22 +1253,8 @@ function csarManager.createCSARforUnit(theUnit, pilotName, radius, silent, score
local csarPoint = dcsCommon.randomPointInCircle(radius, radius/2, point.x, point.z) local csarPoint = dcsCommon.randomPointInCircle(radius, radius/2, point.x, point.z)
-- check the ground- water will kill the pilot
-- not any more! pilot can float
csarPoint.y = csarPoint.z csarPoint.y = csarPoint.z
local surf = land.getSurfaceType(csarPoint) local surf = land.getSurfaceType(csarPoint)
--[[--
if surf == 2 or surf == 3 then
if not silent then
trigger.action.outTextForCoalition(coal, "Bad chute! Bad chute! ".. pilotName .. " did not survive ejection out of their " .. theUnit:getTypeName(), 30)
trigger.action.outSoundForGroup(coal, csarManager.actionSound) -- "Quest Snare 3.wav")
end
return
end
--]]--
csarPoint.y = land.getHeight(csarPoint) csarPoint.y = land.getHeight(csarPoint)
-- when we get here, the terrain is ok, so let's drop the pilot -- when we get here, the terrain is ok, so let's drop the pilot
@ -1278,10 +1281,8 @@ function csarManager.createCSARForParachutist(theUnit, name) -- invoked with par
-- create a CSAR mission now -- create a CSAR mission now
local theMission = csarManager.createCSARMissionData(pos, coa, nil, name, nil, nil, nil, 0.1, nil) local theMission = csarManager.createCSARMissionData(pos, coa, nil, name, nil, nil, nil, 0.1, nil)
csarManager.addMission(theMission) csarManager.addMission(theMission)
-- if not silent then
trigger.action.outTextForCoalition(coa, "MAYDAY MAYDAY MAYDAY! ".. name .. " requesting extraction after eject!", 30) trigger.action.outTextForCoalition(coa, "MAYDAY MAYDAY MAYDAY! ".. name .. " requesting extraction after eject!", 30)
trigger.action.outSoundForGroup(coa, csarManager.actionSound) -- "Quest Snare 3.wav") trigger.action.outSoundForGroup(coa, csarManager.actionSound)
-- end
end end
-- --
@ -1290,7 +1291,6 @@ end
function csarManager.processCSARBASE() function csarManager.processCSARBASE()
local csarBases = cfxZones.zonesWithProperty("CSARBASE") local csarBases = cfxZones.zonesWithProperty("CSARBASE")
-- now add all zones to my zones table, and init additional info -- now add all zones to my zones table, and init additional info
-- from properties -- from properties
for k, aZone in pairs(csarBases) do for k, aZone in pairs(csarBases) do
@ -1305,54 +1305,58 @@ end
function csarManager.readCSARZone(theZone) function csarManager.readCSARZone(theZone)
-- zones have attribute "CSAR" -- zones have attribute "CSAR"
-- gather data, and then create a mission from this -- gather data, and then create a mission from this
local mName = cfxZones.getStringFromZoneProperty(theZone, "CSAR", "Lt. Unknown") local mName = theZone:getStringFromZoneProperty("CSAR", theZone.name)
if mName == "" then mName = theZone.name end -- if mName == "" then mName = theZone.name end
local theSide = cfxZones.getCoalitionFromZoneProperty(theZone, "coalition", 0) local theSide = theZone:getCoalitionFromZoneProperty("coalition", 0)
theZone.csarSide = theSide theZone.csarSide = theSide
theZone.csarName = mName -- now deprecating name attributes theZone.csarName = mName -- now deprecating name attributes
if cfxZones.hasProperty(theZone, "name") then if theZone:hasProperty("name") then
theZone.csarName = cfxZones.getStringFromZoneProperty(theZone, "name", "<none>") theZone.csarName = theZone:getStringFromZoneProperty("name", "<none>")
elseif cfxZones.hasProperty(theZone, "csarName") then elseif theZone:hasProperty("csarName") then
theZone.csarName = cfxZones.getStringFromZoneProperty(theZone, "csarName", "<none>") theZone.csarName = theZone:getStringFromZoneProperty("csarName", "<none>")
elseif cfxZones.hasProperty(theZone, "pilotName") then elseif theZone:hasProperty("pilotName") then
theZone.csarName = cfxZones.getStringFromZoneProperty(theZone, "pilotName", "<none>") theZone.csarName = theZone:getStringFromZoneProperty("pilotName", "<none>")
elseif cfxZones.hasProperty(theZone, "victimName") then elseif theZone:hasProperty("victimName") then
theZone.csarName = cfxZones.getStringFromZoneProperty(theZone, "victimName", "<none>") theZone.csarName = theZone:getStringFromZoneProperty("victimName", "<none>")
end end
theZone.csarFreq = cfxZones.getNumberFromZoneProperty(theZone, "freq", 0) theZone.csarFreq = theZone:getNumberFromZoneProperty("freq", 0)
-- since freqs are set in 10kHz multiplier by DML -- since freqs are set in 10kHz multiplier by DML
-- we have to divide the feq given here by 10 -- we have to divide the feq given here by 10
theZone.csarFreq = theZone.csarFreq / 10 theZone.csarFreq = theZone.csarFreq / 10
if theZone.csarFreq < 0.01 then theZone.csarFreq = nil end if theZone.csarFreq < 0.01 then theZone.csarFreq = nil end
theZone.numCrew = 1 theZone.numCrew = 1
theZone.csarMapMarker = nil theZone.csarMapMarker = nil
theZone.timeLimit = cfxZones.getNumberFromZoneProperty(theZone, "timeLimit", 0) theZone.timeLimit = theZone:getNumberFromZoneProperty("timeLimit", 0)
if theZone.timeLimit == 0 then theZone.timeLimit = nil else theZone.timeLimit = timeLimit * 60 end if theZone.timeLimit == 0 then theZone.timeLimit = nil else theZone.timeLimit = timeLimit * 60 end
local deferred = cfxZones.getBoolFromZoneProperty(theZone, "deferred", false) local deferred = theZone:getBoolFromZoneProperty("deferred", false)
if cfxZones.hasProperty(theZone, "in?") then if theZone:hasProperty("in?") then
theZone.startCSAR = cfxZones.getStringFromZoneProperty(theZone, "in?", "*none") theZone.startCSAR = theZone:getStringFromZoneProperty("in?", "*none")
theZone.lastCSARVal = cfxZones.getFlagValue(theZone.startCSAR, theZone) theZone.lastCSARVal = theZone:getFlagValue(theZone.startCSAR)
elseif theZone:hasProperty("start?") then
theZone.startCSAR = theZone:getStringFromZoneProperty("start?", "*none")
theZone.lastCSARVal = theZone:getFlagValue(theZone.startCSAR)
elseif theZone:hasProperty("startCSAR?") then
theZone.startCSAR = theZone:getStringFromZoneProperty("startCSAR?", "*none")
theZone.lastCSARVal = theZone:getFlagValue(theZone.startCSAR)
end end
if cfxZones.hasProperty(theZone, "start?") then if theZone:hasProperty("score") then
theZone.startCSAR = cfxZones.getStringFromZoneProperty(theZone, "start?", "*none") theZone.score = theZone:getNumberFromZoneProperty("score", 100)
theZone.lastCSARVal = cfxZones.getFlagValue(theZone.startCSAR, theZone)
end end
if cfxZones.hasProperty(theZone, "startCSAR?") then theZone.triggerMethod = theZone:getStringFromZoneProperty("triggerMethod", "change")
theZone.startCSAR = cfxZones.getStringFromZoneProperty(theZone, "startCSAR?", "*none") theZone.rndLoc = theZone:getBoolFromZoneProperty("rndLoc", true)
theZone.lastCSARVal = cfxZones.getFlagValue(theZone.startCSAR, theZone) theZone.onRoad = theZone:getBoolFromZoneProperty("onRoad", false)
end
if cfxZones.hasProperty(theZone, "score") then
theZone.score = cfxZones.getNumberFromZoneProperty(theZone, "score", 100)
end
if (not deferred) then if (not deferred) then
local mPoint = cfxZones.createRandomPointInZone(theZone) local mPoint = theZone:getPoint()
if theZone.rndLoc then mPoint = theZone:createRandomPointInZone() end
if theZone.onRoad then
mPoint.x, mPoint.z = land.getClosestPointOnRoads('roads',mPoint.x, mPoint.z)
end
local theMission = csarManager.createCSARMissionData( local theMission = csarManager.createCSARMissionData(
mPoint, mPoint,
theZone.csarSide, theZone.csarSide,
@ -1379,13 +1383,10 @@ end
function csarManager.processCSARZones() function csarManager.processCSARZones()
local csarBases = cfxZones.zonesWithProperty("CSAR") local csarBases = cfxZones.zonesWithProperty("CSAR")
-- now add all zones to my zones table, and init additional info -- now add all zones to my zones table, and init additional info
-- from properties -- from properties
for k, aZone in pairs(csarBases) do for k, aZone in pairs(csarBases) do
csarManager.readCSARZone(aZone) csarManager.readCSARZone(aZone)
end end
end end
@ -1406,6 +1407,7 @@ function csarManager.installCallback(theCB)
end end
function csarManager.readConfigZone() function csarManager.readConfigZone()
csarManager.name = "csarManagerConfig" -- compat with cfxZones
local theZone = cfxZones.getZoneByName("csarManagerConfig") local theZone = cfxZones.getZoneByName("csarManagerConfig")
if not theZone then if not theZone then
if csarManager.verbose then if csarManager.verbose then
@ -1415,48 +1417,46 @@ function csarManager.readConfigZone()
end end
csarManager.configZone = theZone -- save for flag banging compatibility csarManager.configZone = theZone -- save for flag banging compatibility
csarManager.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false) csarManager.verbose = theZone.verbose
csarManager.ups = theZone:getNumberFromZoneProperty("ups", 1)
csarManager.ups = cfxZones.getNumberFromZoneProperty(theZone, "ups", 1) csarManager.useSmoke = theZone:getBoolFromZoneProperty("useSmoke", true)
csarManager.smokeColor = theZone:getSmokeColorStringFromZoneProperty("smokeColor", "blue")
csarManager.useSmoke = cfxZones.getBoolFromZoneProperty(theZone, "useSmoke", true) csarManager.smokeDist = theZone:getNumberFromZoneProperty("smokeDist", 30)
csarManager.smokeColor = cfxZones.getSmokeColorStringFromZoneProperty(theZone, "smokeColor", "blue")
csarManager.smokeColor = dcsCommon.smokeColor2Num(csarManager.smokeColor) csarManager.smokeColor = dcsCommon.smokeColor2Num(csarManager.smokeColor)
csarManager.useFlare = cfxZones.getBoolFromZoneProperty(theZone, "useFlare", true) csarManager.useFlare = theZone:getBoolFromZoneProperty("useFlare", true)
csarManager.flareColor = cfxZones.getFlareColorStringFromZoneProperty(theZone, "flareColor", "red") csarManager.flareColor = theZone:getFlareColorStringFromZoneProperty("flareColor", "red")
csarManager.flareColor = dcsCommon.flareColor2Num(csarManager.flareColor) csarManager.flareColor = dcsCommon.flareColor2Num(csarManager.flareColor)
if theZone:hasProperty("csarRedDelivered!") then
if cfxZones.hasProperty(theZone, "csarRedDelivered!") then csarManager.csarRedDelivered = theZone:getStringFromZoneProperty("csarRedDelivered!", "*<none>")
csarManager.csarRedDelivered = cfxZones.getStringFromZoneProperty(theZone, "csarRedDelivered!", "*<none>")
end end
if cfxZones.hasProperty(theZone, "csarBlueDelivered!") then if theZone:hasProperty("csarBlueDelivered!") then
csarManager.csarBlueDelivered = cfxZones.getStringFromZoneProperty(theZone, "csarBlueDelivered!", "*<none>") csarManager.csarBlueDelivered = theZone:getStringFromZoneProperty("csarBlueDelivered!", "*<none>")
end end
if cfxZones.hasProperty(theZone, "csarDelivered!") then if theZone:hasProperty("csarDelivered!") then
csarManager.csarDelivered = cfxZones.getStringFromZoneProperty(theZone, "csarDelivered!", "*<none>") csarManager.csarDelivered = theZone:getStringFromZoneProperty("csarDelivered!", "*<none>")
end end
csarManager.rescueRadius = cfxZones.getNumberFromZoneProperty(theZone, "rescueRadius", 70) --70 -- must land within 50m to rescue csarManager.rescueRadius = theZone:getNumberFromZoneProperty( "rescueRadius", 70)
csarManager.hoverRadius = cfxZones.getNumberFromZoneProperty(theZone, "hoverRadius", 30) -- 30 -- must hover within 10m of unit csarManager.hoverRadius = theZone:getNumberFromZoneProperty( "hoverRadius", 30)
csarManager.hoverAlt = cfxZones.getNumberFromZoneProperty(theZone, "hoverAlt", 40) -- 40 -- must hover below this alt csarManager.hoverAlt = theZone:getNumberFromZoneProperty("hoverAlt", 40)
csarManager.hoverDuration = cfxZones.getNumberFromZoneProperty(theZone, "hoverDuration", 20) -- 20 -- must hover for this duration csarManager.hoverDuration = theZone:getNumberFromZoneProperty( "hoverDuration", 20)
csarManager.rescueTriggerRange = cfxZones.getNumberFromZoneProperty(theZone, "rescueTriggerRange", 2000) -- 2000 -- when the unit pops smoke and radios csarManager.rescueTriggerRange = theZone:getNumberFromZoneProperty("rescueTriggerRange", 2000)
csarManager.beaconSound = cfxZones.getStringFromZoneProperty(theZone, "beaconSound", "Radio_beacon_of_distress_on_121.ogg") --"Radio_beacon_of_distress_on_121,5_MHz.ogg" csarManager.beaconSound = theZone:getStringFromZoneProperty( "beaconSound", "Radio_beacon_of_distress_on_121,5_MHz.ogg")
csarManager.pilotWeight = cfxZones.getNumberFromZoneProperty(theZone, "pilotWeight", 120) -- 120 csarManager.pilotWeight = theZone:getNumberFromZoneProperty("pilotWeight", 120)
csarManager.rescueScore = cfxZones.getNumberFromZoneProperty(theZone, "rescueScore", 100) csarManager.rescueScore = theZone:getNumberFromZoneProperty( "rescueScore", 100)
csarManager.actionSound = cfxZones.getStringFromZoneProperty(theZone, "actionSound", "Quest Snare 3.wav") csarManager.actionSound = theZone:getStringFromZoneProperty( "actionSound", "Quest Snare 3.wav")
csarManager.vectoring = cfxZones.getBoolFromZoneProperty(theZone, "vectoring", true) csarManager.vectoring = theZone:getBoolFromZoneProperty("vectoring", true)
-- add own troop carriers -- add own troop carriers
if cfxZones.hasProperty(theZone, "troopCarriers") then if theZone:hasProperty("troopCarriers") then
local tc = cfxZones.getStringFromZoneProperty(theZone, "troopCarriers", "UH-1D") local tc = theZone:getStringFromZoneProperty("troopCarriers", "UH-1D")
tc = dcsCommon.splitString(tc, ",") tc = dcsCommon.splitString(tc, ",")
csarManager.troopCarriers = dcsCommon.trimArray(tc) csarManager.troopCarriers = dcsCommon.trimArray(tc)
if csarManager.verbose then if csarManager.verbose then
@ -1467,6 +1467,8 @@ function csarManager.readConfigZone()
end end
end end
csarManager.addPrefix = theZone:getBoolFromZoneProperty("addPrefix", true)
if csarManager.verbose then if csarManager.verbose then
trigger.action.outText("+++csar: read config", 30) trigger.action.outText("+++csar: read config", 30)
end end
@ -1536,5 +1538,5 @@ end
-- allow any airfied to be csarsafe by default, no longer *requires* csarbase -- allow any airfied to be csarsafe by default, no longer *requires* csarbase
-- support quad zones and optionally non-random placement -- remove cfxPlayer dependency
--]]-- --]]--

View File

@ -1,5 +1,5 @@
dcsCommon = {} dcsCommon = {}
dcsCommon.version = "2.9.3" dcsCommon.version = "2.9.5"
--[[-- VERSION HISTORY --[[-- VERSION HISTORY
2.2.6 - compassPositionOfARelativeToB 2.2.6 - compassPositionOfARelativeToB
- clockPositionOfARelativeToB - clockPositionOfARelativeToB
@ -173,6 +173,9 @@ dcsCommon.version = "2.9.3"
- new getCountriesForCoalition() - new getCountriesForCoalition()
2.9.2 - updated event2text 2.9.2 - updated event2text
2.9.3 - getAirbasesWhoseNameContains now supports category tables for filtering 2.9.3 - getAirbasesWhoseNameContains now supports category tables for filtering
2.9.4 - new bearing2degrees()
2.9.5 - distanceOfPointPToLineXZ(p, p1, p2)
--]]-- --]]--
-- dcsCommon is a library of common lua functions -- dcsCommon is a library of common lua functions
@ -845,6 +848,13 @@ dcsCommon.version = "2.9.3"
return "North" return "North"
end end
function dcsCommon.bearing2degrees(inRad)
local degrees = inRad / math.pi * 180
if degrees < 0 then degrees = degrees + 360 end
if degrees > 360 then degrees = degrees - 360 end
return degrees
end
function dcsCommon.bearing2compass(inrad) function dcsCommon.bearing2compass(inrad)
local bearing = math.floor(inrad / math.pi * 180) local bearing = math.floor(inrad / math.pi * 180)
if bearing < 0 then bearing = bearing + 360 end if bearing < 0 then bearing = bearing + 360 end
@ -977,6 +987,19 @@ dcsCommon.version = "2.9.3"
-- note: no separate case for straight in front or behind -- note: no separate case for straight in front or behind
end end
-- Distance of point p to line defined by p1,p2
-- only on XZ map
function dcsCommon.distanceOfPointPToLineXZ(p, p1, p2)
local x21 = p2.x - p1.x
local y10 = p1.z - p.z
local x10 = p1.x - p.x
local y21 = p2.z - p1.z
local numer = math.abs((x21*y10) - (x10 * y21))
local denom = math.sqrt(x21 * x21 + y21 * y21)
local dist = numer/denom
return dist
end
function dcsCommon.randomDegrees() function dcsCommon.randomDegrees()
local degrees = math.random(360) * 3.14152 / 180 local degrees = math.random(360) * 3.14152 / 180
return degrees return degrees

View File

@ -1,5 +1,5 @@
delayFlag = {} delayFlag = {}
delayFlag.version = "1.3.0" delayFlag.version = "1.4.0"
delayFlag.verbose = false delayFlag.verbose = false
delayFlag.requiredLibs = { delayFlag.requiredLibs = {
"dcsCommon", -- always "dcsCommon", -- always
@ -38,6 +38,8 @@ delayFlag.flags = {}
- continueDelay - continueDelay
- delayLeft - delayLeft
1.3.0 - persistence 1.3.0 - persistence
1.4.0 - dmlZones
- delayLeft#
--]]-- --]]--
@ -64,70 +66,70 @@ end
-- --
function delayFlag.createTimerWithZone(theZone) function delayFlag.createTimerWithZone(theZone)
-- delay -- delay
theZone.delayMin, theZone.delayMax = cfxZones.getPositiveRangeFromZoneProperty(theZone, "timeDelay", 1) -- same as zone signature theZone.delayMin, theZone.delayMax = theZone:getPositiveRangeFromZoneProperty("timeDelay", 1) -- same as zone signature
if delayFlag.verbose or theZone.verbose then if delayFlag.verbose or theZone.verbose then
trigger.action.outText("+++dlyF: time delay is <" .. theZone.delayMin .. ", " .. theZone.delayMax .. "> seconds", 30) trigger.action.outText("+++dlyF: time delay is <" .. theZone.delayMin .. ", " .. theZone.delayMax .. "> seconds", 30)
end end
-- watchflags: -- watchflags:
-- triggerMethod -- triggerMethod
theZone.delayTriggerMethod = cfxZones.getStringFromZoneProperty(theZone, "triggerMethod", "change") theZone.delayTriggerMethod = theZone:getStringFromZoneProperty("triggerMethod", "change")
if cfxZones.hasProperty(theZone, "delayTriggerMethod") then if theZone:hasProperty("delayTriggerMethod") then
theZone.delayTriggerMethod = cfxZones.getStringFromZoneProperty(theZone, "delayTriggerMethod", "change") theZone.delayTriggerMethod = theZone:getStringFromZoneProperty("delayTriggerMethod", "change")
end end
-- trigger flag -- trigger flag
if cfxZones.hasProperty(theZone, "f?") then if theZone:hasProperty("f?") then
theZone.triggerDelayFlag = cfxZones.getStringFromZoneProperty(theZone, "f?", "none") theZone.triggerDelayFlag = theZone:getStringFromZoneProperty("f?", "none")
end elseif theZone:hasProperty("in?") then
theZone.triggerDelayFlag = theZone:getStringFromZoneProperty("in?", "none")
if cfxZones.hasProperty(theZone, "in?") then elseif theZone:hasProperty("startDelay?") then
theZone.triggerDelayFlag = cfxZones.getStringFromZoneProperty(theZone, "in?", "none") theZone.triggerDelayFlag = theZone:getStringFromZoneProperty("startDelay?", "none")
end
if cfxZones.hasProperty(theZone, "startDelay?") then
theZone.triggerDelayFlag = cfxZones.getStringFromZoneProperty(theZone, "startDelay?", "none")
end end
if theZone.triggerDelayFlag then if theZone.triggerDelayFlag then
theZone.lastDelayTriggerValue = cfxZones.getFlagValue(theZone.triggerDelayFlag, theZone) -- trigger.misc.getUserFlag(theZone.triggerDelayFlag) -- save last value theZone.lastDelayTriggerValue = theZone:getFlagValue(theZone.triggerDelayFlag)
end end
theZone.delayMethod = cfxZones.getStringFromZoneProperty(theZone, "method", "inc") theZone.delayMethod = theZone:getStringFromZoneProperty("method", "inc")
if cfxZones.hasProperty(theZone, "delayMethod") then if theZone:hasProperty("delayMethod") then
theZone.delayMethod = cfxZones.getStringFromZoneProperty(theZone, "delayMethod", "inc") theZone.delayMethod = theZone:getStringFromZoneProperty( "delayMethod", "inc")
end end
-- out flag -- out flag
theZone.delayDoneFlag = cfxZones.getStringFromZoneProperty(theZone, "out!", "*<none>") theZone.delayDoneFlag = theZone:getStringFromZoneProperty("out!", "*<none>")
if cfxZones.hasProperty(theZone, "delayDone!") then if theZone:hasProperty("delayDone!") then
theZone.delayDoneFlag = cfxZones.getStringFromZoneProperty(theZone, "delayDone!", "*<none>") theZone.delayDoneFlag = theZone:getStringFromZoneProperty( "delayDone!", "*<none>")
end end
-- stop the press! -- stop the press!
if cfxZones.hasProperty(theZone, "stopDelay?") then if theZone:hasProperty("stopDelay?") then
theZone.triggerStopDelay = cfxZones.getStringFromZoneProperty(theZone, "stopDelay?", "none") theZone.triggerStopDelay = theZone:getStringFromZoneProperty("stopDelay?", "none")
theZone.lastTriggerStopValue = cfxZones.getFlagValue(theZone.triggerStopDelay, theZone) theZone.lastTriggerStopValue = theZone:getFlagValue(theZone.triggerStopDelay)
end end
-- pause and continue -- pause and continue
if cfxZones.hasProperty(theZone, "pauseDelay?") then if theZone:hasProperty("pauseDelay?") then
theZone.triggerPauseDelay = cfxZones.getStringFromZoneProperty(theZone, "pauseDelay?", "none") theZone.triggerPauseDelay = theZone:getStringFromZoneProperty("pauseDelay?", "none")
theZone.lastTriggerPauseValue = cfxZones.getFlagValue(theZone.triggerPauseDelay, theZone) theZone.lastTriggerPauseValue = theZone:getFlagValue(theZone.triggerPauseDelay)
end end
if cfxZones.hasProperty(theZone, "continueDelay?") then if theZone:hasProperty("continueDelay?") then
theZone.triggerContinueDelay = cfxZones.getStringFromZoneProperty(theZone, "continueDelay?", "none") theZone.triggerContinueDelay = theZone:getStringFromZoneProperty("continueDelay?", "none")
theZone.lastTriggerContinueValue = cfxZones.getFlagValue(theZone.triggerContinueDelay, theZone) theZone.lastTriggerContinueValue = theZone:getFlagValue(theZone.triggerContinueDelay)
end end
-- timeInfo -- timeInfo
theZone.delayTimeLeft = cfxZones.getStringFromZoneProperty(theZone, "delayLeft", "*cfxIgnored") if theZone:hasProperty("delayLeft") then
theZone.delayTimeLeft = theZone:getStringFromZoneProperty("delayLeft", "*cfxIgnored")
else
theZone.delayTimeLeft = theZone:getStringFromZoneProperty("delayLeft#", "*cfxIgnored")
end
-- init -- init
theZone.delayRunning = false theZone.delayRunning = false
@ -136,7 +138,7 @@ function delayFlag.createTimerWithZone(theZone)
theZone.timeLeft = -1 -- in seconds, always kept up to date theZone.timeLeft = -1 -- in seconds, always kept up to date
-- but not really used -- but not really used
cfxZones.setFlagValue(theZone.delayTimeLeft, -1, theZone) theZone:setFlagValue(theZone.delayTimeLeft, -1)
end end
@ -167,7 +169,7 @@ function delayFlag.startDelay(theZone)
end end
theZone.timeLimit = timer.getTime() + delay theZone.timeLimit = timer.getTime() + delay
cfxZones.setFlagValue(theZone.delayTimeLeft, delay, theZone) theZone:setFlagValue(theZone.delayTimeLeft, delay)
end end
function delayFlag.pauseDelay(theZone) function delayFlag.pauseDelay(theZone)
@ -194,7 +196,7 @@ function delayFlag.update()
if remaining < 0 then remaining = -1 end if remaining < 0 then remaining = -1 end
-- see if we need to stop -- see if we need to stop
if cfxZones.testZoneFlag(aZone, aZone.triggerStopDelay, aZone.delayTriggerMethod, "lastTriggerStopValue") then if aZone:testZoneFlag(aZone.triggerStopDelay, aZone.delayTriggerMethod, "lastTriggerStopValue") then
aZone.delayRunning = false -- simply stop. aZone.delayRunning = false -- simply stop.
if delayFlag.verbose or aZone.verbose then if delayFlag.verbose or aZone.verbose then
trigger.action.outText("+++dlyF: stopped delay " .. aZone.name, 30) trigger.action.outText("+++dlyF: stopped delay " .. aZone.name, 30)
@ -202,7 +204,7 @@ function delayFlag.update()
end end
if cfxZones.testZoneFlag(aZone, aZone.triggerDelayFlag, aZone.delayTriggerMethod, "lastDelayTriggerValue") then if aZone:testZoneFlag(aZone.triggerDelayFlag, aZone.delayTriggerMethod, "lastDelayTriggerValue") then
if delayFlag.verbose or aZone.verbose then if delayFlag.verbose or aZone.verbose then
if aZone.delayRunning then if aZone.delayRunning then
trigger.action.outText("+++dlyF: re-starting timer " .. aZone.name, 30) trigger.action.outText("+++dlyF: re-starting timer " .. aZone.name, 30)
@ -216,7 +218,7 @@ function delayFlag.update()
if not aZone.delayPaused then if not aZone.delayPaused then
if aZone.delayRunning and cfxZones.testZoneFlag(aZone, aZone.triggerPauseDelay, aZone.delayTriggerMethod, "lastTriggerPauseValue") then if aZone.delayRunning and aZone:testZoneFlag( aZone.triggerPauseDelay, aZone.delayTriggerMethod, "lastTriggerPauseValue") then
if delayFlag.verbose or aZone.verbose then if delayFlag.verbose or aZone.verbose then
trigger.action.outText("+++dlyF: pausing timer <" .. aZone.name .. "> with <" .. remaining .. "> remaining", 30) trigger.action.outText("+++dlyF: pausing timer <" .. aZone.name .. "> with <" .. remaining .. "> remaining", 30)
end end
@ -232,14 +234,14 @@ function delayFlag.update()
if delayFlag.verbose or aZone.verbose then if delayFlag.verbose or aZone.verbose then
trigger.action.outText("+++dlyF: banging on " .. aZone.delayDoneFlag, 30) trigger.action.outText("+++dlyF: banging on " .. aZone.delayDoneFlag, 30)
end end
cfxZones.pollFlag(aZone.delayDoneFlag, aZone.delayMethod, aZone) aZone:pollFlag(aZone.delayDoneFlag, aZone.delayMethod)
end end
end end
cfxZones.setFlagValue(aZone.delayTimeLeft, remaining, aZone) aZone:setFlagValue(aZone.delayTimeLeft, remaining)
else else
-- we are paused. Check for 'continue' -- we are paused. Check for 'continue'
if aZone.delayRunning and cfxZones.testZoneFlag(aZone, aZone.triggerContinueDelay, aZone.delayTriggerMethod, "lastTriggerContinueValue") then if aZone.delayRunning and aZone:testZoneFlag( aZone.triggerContinueDelay, aZone.delayTriggerMethod, "lastTriggerContinueValue") then
if delayFlag.verbose or aZone.verbose then if delayFlag.verbose or aZone.verbose then
trigger.action.outText("+++dlyF: continuing timer <" .. aZone.name .. "> with <" .. aZone.remainingTime .. "> seconds remaining", 30) trigger.action.outText("+++dlyF: continuing timer <" .. aZone.name .. "> with <" .. aZone.remainingTime .. "> seconds remaining", 30)
end end
@ -312,13 +314,10 @@ end
function delayFlag.readConfigZone() function delayFlag.readConfigZone()
local theZone = cfxZones.getZoneByName("delayFlagsConfig") local theZone = cfxZones.getZoneByName("delayFlagsConfig")
if not theZone then if not theZone then
if delayFlag.verbose then theZone = cfxZones.createSimpleZone("delayFlagsConfig")
trigger.action.outText("+++dlyF: NO config zone!", 30)
end
return
end end
delayFlag.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false) delayFlag.verbose = theZone.verbose -- cfxZones.getBoolFromZoneProperty(theZone, "verbose", false)
if delayFlag.verbose then if delayFlag.verbose then
trigger.action.outText("+++dlyF: read config", 30) trigger.action.outText("+++dlyF: read config", 30)

View File

@ -111,7 +111,7 @@ function mxObjects.showNClosestTextObjectsToUnit(n, theUnit, numbered)
-- dist -- dist
msg = msg .. " " .. dist .. units msg = msg .. " " .. dist .. units
msg = msg .. "\n" -- add line feed msg = msg .. "\n" -- add line feed
if mxObjects.doubleLine then msg = msg .. "\n" end if mxObjects.doubleLine and (i < n) then msg = msg .. "\n" end
end end
end end
return msg return msg

View File

@ -51,7 +51,7 @@ function ownAll.ownAllForZone(theZone)
theZone:setFlagValue(theZone.totalNum, #filtered) theZone:setFlagValue(theZone.totalNum, #filtered)
end end
theZone.ownershipUplink = theZone:getBoolFromZoneProperty("uplink", true) theZone.ownershipUplink = theZone:getBoolFromZoneProperty("uplink", true) -- not documented
local redNum, blueNum local redNum, blueNum
theZone.state, redNum, blueNum = ownAll.calcState(theZone) theZone.state, redNum, blueNum = ownAll.calcState(theZone)

View File

@ -1,5 +1,5 @@
radioMenu = {} radioMenu = {}
radioMenu.version = "2.1.0" radioMenu.version = "2.1.1"
radioMenu.verbose = false radioMenu.verbose = false
radioMenu.ups = 1 radioMenu.ups = 1
radioMenu.requiredLibs = { radioMenu.requiredLibs = {
@ -31,6 +31,7 @@ radioMenu.menus = {}
ackA, ackB, ackC, ackD attributes ackA, ackB, ackC, ackD attributes
valA-D now define full method, not just values valA-D now define full method, not just values
full wildcard support for ack and cooldown full wildcard support for ack and cooldown
2.1.1 - outMessage now works correctly
--]]-- --]]--
function radioMenu.addRadioMenu(theZone) function radioMenu.addRadioMenu(theZone)
@ -355,7 +356,7 @@ function radioMenu.radioOutMsg(ack, gid, theZone)
-- group processing. only if gid>0 and cfxMX -- group processing. only if gid>0 and cfxMX
local theMsg = ack local theMsg = ack
if (gid > 0) and cfxMX then if (gid > 0) and cfxMX then
local gName = cfxMX.cfxMX.groupNamesByID[gid] local gName = cfxMX.groupNamesByID[gid]
theMsg = theMsg:gsub("<group>", gName) theMsg = theMsg:gsub("<group>", gName)
end end

Binary file not shown.