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 = {
"dcsCommon",
"cfxZones", -- Zones, of course
-- "cfxCommander", -- to make troops do stuff
-- "cfxGroundTroops", -- generic when dropping troops
}
-- *** 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.version = "1.2.1"
cfxNDB.version = "1.3.0"
--[[--
cfxNDB:
@ -26,6 +26,7 @@ cfxNDB.version = "1.2.1"
- update only when moving and delta > maxDelta
- zone-local verbosity support
- better config defaulting
1.3.0 - dmlZones
--]]--
@ -104,42 +105,42 @@ function cfxNDB.stopNDB(theNDB)
end
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
theZone.freq = theZone.freq * 1000000 -- Hz
theZone.fm = cfxZones.getBoolFromZoneProperty(theZone, "fm", false)
theZone.ndbSound = cfxZones.getStringFromZoneProperty(theZone, "soundFile", "<none>")
theZone.power = cfxZones.getNumberFromZoneProperty(theZone, "watts", cfxNDB.power)
theZone.fm = theZone:getBoolFromZoneProperty("fm", false)
theZone.ndbSound = theZone:getStringFromZoneProperty("soundFile", "<none>")
theZone.power = theZone:getNumberFromZoneProperty("watts", cfxNDB.power)
theZone.loop = true -- always. NDB always loops
-- UNSUPPORTED refresh. Although read individually, it only works
-- 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
-- paused
theZone.paused = cfxZones.getBoolFromZoneProperty(theZone, "paused", false)
theZone.paused = theZone:getBoolFromZoneProperty("paused", false)
-- watchflags
theZone.ndbTriggerMethod = cfxZones.getStringFromZoneProperty(theZone, "triggerMethod", "change")
if cfxZones.hasProperty(theZone, "ndbTriggerMethod") then
theZone.ndbTriggerMethod = cfxZones.getStringFromZoneProperty(theZone, "ndbTriggerMethod", "change")
theZone.ndbTriggerMethod = theZone:getStringFromZoneProperty( "triggerMethod", "change")
if theZone:hasProperty("ndbTriggerMethod") then
theZone.ndbTriggerMethod = theZone:getStringFromZoneProperty("ndbTriggerMethod", "change")
end
-- on/offf query flags
if cfxZones.hasProperty(theZone, "on?") then
theZone.onFlag = cfxZones.getStringFromZoneProperty(theZone, "on?", "none")
if theZone:hasProperty("on?") then
theZone.onFlag = theZone:getStringFromZoneProperty("on?", "none")
end
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
if cfxZones.hasProperty(theZone, "off?") then
theZone.offFlag = cfxZones.getStringFromZoneProperty(theZone, "off?", "none")
if theZone:hasProperty("off?") then
theZone.offFlag = theZone:getStringFromZoneProperty("off?", "none")
end
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
-- start it
@ -170,7 +171,7 @@ function cfxNDB.update()
if not theNDB.lastLoc then
cfxNDB.startNDB(theNDB) -- never was started
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
local delta = dcsCommon.dist(loc, theNDB.lastLoc)
if delta > cfxNDB.maxDist then
@ -182,17 +183,15 @@ function cfxNDB.update()
end
-- 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
cfxNDB.startNDB(theNDB)
end
if cfxZones.testZoneFlag(theNDB, theNDB.offFlag, theNDB.ndbTriggerMethod, "offFlagVal") then
if theNDB:testZoneFlag(theNDB.offFlag, theNDB.ndbTriggerMethod, "offFlagVal") then
-- yupp, trigger start
cfxNDB.stopNDB(theNDB)
end
end
end
@ -205,10 +204,10 @@ function cfxNDB.readConfig()
theZone = cfxZones.createSimpleZone("ndbConfig")
end
cfxNDB.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false)
cfxNDB.ndbRefresh = cfxZones.getNumberFromZoneProperty(theZone, "ndbRefresh", 10)
cfxNDB.verbose = theZone.verbose
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
trigger.action.outText("***ndb: read config", 30)

View File

@ -1,5 +1,5 @@
cfxObjectSpawnZones = {}
cfxObjectSpawnZones.version = "1.3.1"
cfxObjectSpawnZones.version = "2.0.0"
cfxObjectSpawnZones.requiredLibs = {
"dcsCommon", -- common is of course needed for everything
-- pretty stupid to check for this since we
@ -8,31 +8,33 @@ cfxObjectSpawnZones.requiredLibs = {
}
cfxObjectSpawnZones.ups = 1
cfxObjectSpawnZones.verbose = false
--
-- Zones that conform with this requirements spawn toops automatically
-- *** 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
--[[--
Zones that conform with this requirements spawn objects automatically
*** 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
2.0.0 - dmlZones
--]]--
-- respawn currently happens after theSpawns is deleted and cooldown seconds have passed
cfxObjectSpawnZones.allSpawners = {}
@ -59,72 +61,70 @@ end
function cfxObjectSpawnZones.createSpawner(inZone)
local theSpawner = {}
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
if cfxZones.hasProperty(inZone, "f?") then
theSpawner.triggerFlag = cfxZones.getStringFromZoneProperty(inZone, "f?", "none")
elseif cfxZones.hasProperty(inZone, "spawn?") then
theSpawner.triggerFlag = cfxZones.getStringFromZoneProperty(inZone, "spawn?", "none")
elseif cfxZones.hasProperty(inZone, "spawnObjects?") then
theSpawner.triggerFlag = cfxZones.getStringFromZoneProperty(inZone, "spawnObjects?", "none")
if inZone:hasProperty("f?") then
theSpawner.triggerFlag = inZone:getStringFromZoneProperty("f?", "none")
elseif inZone:hasProperty("spawn?") then
theSpawner.triggerFlag = inZone:getStringFromZoneProperty("spawn?", "none")
elseif inZone:hasProperty("spawnObjects?") then
theSpawner.triggerFlag = inZone:getStringFromZoneProperty( "spawnObjects?", "none")
end
if theSpawner.triggerFlag then
theSpawner.lastTriggerValue = cfxZones.getFlagValue(theSpawner.triggerFlag, theSpawner) -- trigger.misc.getUserFlag(theSpawner.triggerFlag)
theSpawner.lastTriggerValue = cfxZones.getFlagValue(theSpawner.triggerFlag, theSpawner)
end
if cfxZones.hasProperty(inZone, "activate?") then
theSpawner.activateFlag = cfxZones.getStringFromZoneProperty(inZone, "activate?", "none")
if inZone:hasProperty("activate?") then
theSpawner.activateFlag = inZone:getStringFromZoneProperty( "activate?", "none")
theSpawner.lastActivateValue = cfxZones.getFlagValue(theSpawner.activateFlag, theSpawner) --trigger.misc.getUserFlag(theSpawner.activateFlag)
end
if cfxZones.hasProperty(inZone, "pause?") then
theSpawner.pauseFlag = cfxZones.getStringFromZoneProperty(inZone, "pause?", "none")
theSpawner.lastPauseValue = cfxZones.getFlagValue(theSpawner.lastPauseValue, theSpawner) -- trigger.misc.getUserFlag(theSpawner.pauseFlag)
if inZone:hasProperty("pause?") then
theSpawner.pauseFlag = inZone:getStringFromZoneProperty("pause?", "none")
theSpawner.lastPauseValue = cfxZones.getFlagValue(theSpawner.lastPauseValue, theSpawner)
end
theSpawner.types = cfxZones.getStringFromZoneProperty(inZone, "types", "White_Tyre")
local n = cfxZones.getNumberFromZoneProperty(inZone, "count", 1) -- DO NOT CONFUSE WITH OWN PROPERTY COUNT for unique names!!!
theSpawner.types = inZone:getStringFromZoneProperty("types", "White_Tyre")
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.
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.baseName = cfxZones.getStringFromZoneProperty(inZone, "baseName", dcsCommon.uuid("objSpwn"))
theSpawner.baseName = inZone:getStringFromZoneProperty("baseName", "*")
theSpawner.baseName = dcsCommon.trim(theSpawner.baseName)
if theSpawner.baseName == "*" then
theSpawner.baseName = inZone.name -- convenience shortcut
end
--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.autoRemove = cfxZones.getBoolFromZoneProperty(inZone, "autoRemove", false)
theSpawner.autoLink = cfxZones.getBoolFromZoneProperty(inZone, "autoLink", true)
theSpawner.autoRemove = inZone:getBoolFromZoneProperty("autoRemove", false)
theSpawner.autoLink = inZone:getBoolFromZoneProperty("autoLink", true)
theSpawner.heading = cfxZones.getNumberFromZoneProperty(inZone, "heading", 0)
theSpawner.weight = cfxZones.getNumberFromZoneProperty(inZone, "weight", 0)
theSpawner.heading = inZone:getNumberFromZoneProperty("heading", 0)
theSpawner.weight = inZone:getNumberFromZoneProperty("weight", 0)
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
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.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.theSpawns = {} -- all items that are spawned. re-spawn happens if they are all out
theSpawner.maxSpawns = cfxZones.getNumberFromZoneProperty(inZone, "maxSpawns", 1)
theSpawner.paused = cfxZones.getBoolFromZoneProperty(inZone, "paused", false)
theSpawner.requestable = cfxZones.getBoolFromZoneProperty(inZone, "requestable", false)
theSpawner.maxSpawns = inZone:getNumberFromZoneProperty("maxSpawns", 1)
theSpawner.paused = inZone:getBoolFromZoneProperty("paused", false)
theSpawner.requestable = inZone:getBoolFromZoneProperty("requestable", false)
if theSpawner.requestable then theSpawner.paused = true end
-- see if the spawn can be made brittle/delicte
if cfxZones.hasProperty(inZone, "useDelicates") then
theSpawner.delicateName = dcsCommon.trim(cfxZones.getStringFromZoneProperty(inZone, "useDelicates", "<none>"))
if inZone:hasProperty("useDelicates") then
theSpawner.delicateName = dcsCommon.trim(inZone:getStringFromZoneProperty("useDelicates", "<none>"))
if theSpawner.delicateName == "*" then theSpawner.delicateName = inZone.name end
end

View File

@ -43,11 +43,7 @@ cfxOwnedZones.initialized = false
owned zones is a module that manages conquerable zones and keeps a record
of who owns the zone based on rules
*** 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
*** EXTENTDS ZONES ***
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

View File

@ -1,5 +1,5 @@
cfxZones = {}
cfxZones.version = "4.0.7"
cfxZones.version = "4.0.9"
-- cf/x zone management module
-- reads dcs zones and makes them accessible and mutable
@ -56,7 +56,15 @@ cfxZones.version = "4.0.7"
- processDynamicValueVU
- 4.0.6 - hash mark forgotten QoL
- 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
-- store pRad
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
-- huston, we have a problem
if cfxZones.verbose then
@ -319,28 +327,11 @@ function dmlZone:calculateZoneBounds()
end
function cfxZones.createPoint(x, y, z) -- bridge to dcsCommon, backward comp.
return dcsCommon.createPoint(x, y, z)
--[[--
local newPoint = {}
newPoint.x = x
newPoint.y = y
newPoint.z = z
return newPoint --]]--
return dcsCommon.createPoint(x, y, z)
end
function cfxZones.copyPoint(inPoint) -- bridge to dcsCommon, backward comp.
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
function cfxZones.createHeightCorrectedPoint(inPoint) -- this should be in dcsCommon
@ -535,7 +526,8 @@ function cfxZones.createCircleZone(name, x, z, radius)
newZone.name = name
newZone.radius = radius
newZone.point = dcsCommon.createPoint(x, 0, z)
newZone.dcsOrigin = dcsCommon.createPoint(x, 0, z)
-- props
newZone.properties = {}
@ -552,8 +544,9 @@ function cfxZones.createSimplePolyZone(name, location, points, addToManaged)
end
if not location.x then location.x = 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
cfxZones.addZoneToManagedZones(newZone)
@ -593,8 +586,11 @@ function cfxZones.createSimpleQuadZone(name, location, points, addToManaged)
return cfxZones.createSimplePolyZone(name, location, points, addToManaged)
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
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.isPoly = true
newZone.poly = {}
@ -612,10 +608,9 @@ function cfxZones.createPolyZone(name, poly) -- poly must be array of point type
newZone.properties = {}
cfxZones.calculateZoneBounds(newZone)
return newZone
end
function cfxZones.createRandomZoneInZone(name, inZone, targetRadius, entirelyInside)
-- create a new circular zone with center placed inside inZone
-- 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
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)
-- construct new zone
local newZone = cfxZones.createCircleZone(name, newPoint.x, newPoint.z, targetRadius)
@ -833,7 +811,6 @@ function cfxZones.getZoneVolume(theZone)
}
return vol
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
local lowerLeft = {}
-- we build x = westerm y = southern, Z = alt
@ -870,17 +847,11 @@ end
function cfxZones.declutterZone(theZone)
if not theZone then return end
local theVol = cfxZones.getZoneVolume(theZone)
-- if theZone.verbose then
-- dcsCommon.dumpVar2Str("vol", theVol)
-- end
world.removeJunk(theVol)
end
function dmlZone:declutterZone()
local theVol = cfxZones.getZoneVolume(self)
-- if self.verbose then
-- dcsCommon.dumpVar2Str("vol", theVol)
-- end
world.removeJunk(theVol)
end
@ -888,8 +859,7 @@ end
-- units / groups in zone
--
function cfxZones.allGroupsInZone(theZone, categ) -- categ is optional, must be code
-- warning: does not check for exiting!
--trigger.action.outText("Zone " .. theZone.name .. " radius " .. theZone.radius, 30)
-- warning: does not check for existing!
local inZones = {}
local coals = {0, 1, 2} -- all coalitions
for idx, coa in pairs(coals) do
@ -908,8 +878,7 @@ function dmlZone:allGroupsInZone(categ)
end
function cfxZones.allGroupNamesInZone(theZone, categ) -- categ is optional, must be code
-- warning: does not check for exiting!
--trigger.action.outText("Zone " .. theZone.name .. " radius " .. theZone.radius, 30)
-- warning: does not check for existing!
local inZones = {}
local coals = {0, 1, 2} -- all coalitions
for idx, coa in pairs(coals) do
@ -928,7 +897,7 @@ function dmlZone:allGroupNamesInZone(categ)
end
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 coals = {0, 1, 2} -- all coalitions
for idx, coa in pairs(coals) do
@ -976,11 +945,9 @@ function cfxZones.isGroupPartiallyInZone(aGroup, aZone)
if aUnit:isExist() and aUnit:getLife() > 1 then
local p = aUnit:getPoint()
local inzone, percent, dist = cfxZones.pointInZone(p, aZone)
if inzone then -- cfxZones.isPointInsideZone(p, aZone) then
--trigger.action.outText("zne: YAY <" .. aUnit:getName() .. "> IS IN " .. aZone.name, 30)
if inzone then
return true
end
--trigger.action.outText("zne: <" .. aUnit:getName() .. "> not in " .. aZone.name .. ", dist = " .. dist .. ", rad = ", aZone.radius, 30)
end
end
return false
@ -1073,11 +1040,7 @@ function cfxZones.dumpZones(zoneTable)
end
trigger.action.outText("Zones end", 10)
end
--[[-- moved to dcsCommon
function cfxZones.stringStartsWith(theString, thePrefix)
return theString:find(thePrefix) == 1
end
--]]--
function cfxZones.keysForTable(theTable)
local keyset={}
local n=0
@ -1113,17 +1076,12 @@ end
-- return all zones from the zone table that begin with string prefix
--
function cfxZones.zonesStartingWithName(prefix, searchSet)
if not searchSet then searchSet = cfxZones.zones end
-- trigger.action.outText("Enter: zonesStartingWithName for " .. prefix , 30)
local prefixZones = {}
prefix = prefix:upper() -- all zones have UPPERCASE NAMES! THEY SCREAM AT YOU
for name, zone in pairs(searchSet) do
-- trigger.action.outText("testing " .. name:upper() .. " starts with " .. prefix , 30)
if dcsCommon.stringStartsWith(name:upper(), prefix) then
prefixZones[name] = zone -- note: ref copy!
--trigger.action.outText("zone with prefix <" .. prefix .. "> found: " .. name, 10)
end
end
@ -1396,19 +1354,6 @@ function dmlZone:closestUnitToZoneCenter(theUnits)
return cfxZones.closestUnitToZoneCenter(theUnits, self)
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
function cfxZones.growZone()
-- circular zones simply increase radius
@ -1776,14 +1721,6 @@ end
function dmlZone:getFlagValue(theFlag)
return cfxZones.getFlagValue(theFlag, self)
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)
local lMethod = string.lower(theMethod)
@ -2082,7 +2019,6 @@ function cfxZones.testZoneFlag(theZone, theFlagName, theMethod, latchName)
return false, currVal
end
--trigger.action.outText("+++Zne: about to test: c = " .. currVal .. ", l = " .. lastVal, 30)
local testResult = cfxZones.testFlagByMethodForZone(currVal, lastVal, theMethod, theZone)
-- update latch by method
@ -2197,12 +2133,10 @@ end
function cfxZones.getZoneProperty(cZone, theKey)
if not cZone then
trigger.action.outText("+++zone: no zone in getZoneProperty", 30)
-- breek.here.noew = 1
return nil
end
if not theKey then
trigger.action.outText("+++zone: no property key in getZoneProperty for zone " .. cZone.name, 30)
-- breakme.here = 1
return
end
@ -2224,7 +2158,6 @@ end
function cfxZones.getStringFromZoneProperty(theZone, theProperty, default)
if not default then default = "" end
-- local p = cfxZones.getZoneProperty(theZone, theProperty)
-- OOP heavy duty test here
local p = theZone:getZoneProperty(theProperty)
if not p then return default end
@ -2378,7 +2311,6 @@ function cfxZones.hasProperty(theZone, theProperty)
return false
end
return true
-- return foundIt ~= nil
end
function dmlZone:hasProperty(theProperty)
@ -2539,30 +2471,22 @@ function dmlZone:getCoalitionFromZoneProperty(theProperty, default)
end
function cfxZones.getNumberFromZoneProperty(theZone, theProperty, default)
--TODO: trim string
if not default then default = 0 end
default = tonumber(default)
if not default then default = 0 end -- enforce default numbner as well
local p = cfxZones.getZoneProperty(theZone, theProperty)
p = tonumber(p)
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
end
function dmlZone:getNumberFromZoneProperty(theProperty, default)
--TODO: trim string
function dmlZone:getNumberFromZoneProperty(theProperty, default)
if not default then default = 0 end
default = tonumber(default)
if not default then default = 0 end -- enforce default numbner as well
local p = self:getZoneProperty(theProperty)
p = tonumber(p)
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
end
@ -2883,7 +2807,6 @@ function cfxZones.processDynamicValues(inMsg, theZone, msgResponses)
repeat -- iterate all patterns one by one
local startLoc, endLoc = string.find(outMsg, pattern)
if startLoc then
--trigger.action.outText("response: found an occurence", 30)
local theValParam = string.sub(outMsg, startLoc, endLoc)
-- strip lead and trailer
local param = string.gsub(theValParam, "<rsp:%s*", "")
@ -3131,7 +3054,6 @@ function cfxZones.processDynamicAB(inMsg, locale)
left = dcsCommon.trim(left)
right = string.sub(rp, rightA+1, rightB-1)
right = dcsCommon.trim(right)
-- trigger.action.outText("+++replacer pattern <" .. rp .. "> found, val = <" .. val .. ">, A = <" .. left .. ">, B = <" .. right .. ">", 30)
local yesno = false
-- see if unit exists
local theUnit = Unit.getByName(val)
@ -3336,7 +3258,6 @@ function cfxZones.linkUnitToZone(theUnit, theZone, dx, dy) -- note: dy is really
-- direction to zone
theZone.uHdg = unitHeading -- original unit heading to turn other
-- units if need be
--trigger.action.outText("Link setup: dx=<" .. dx .. ">, dy=<" .. dy .. "> unit original hdg = <" .. math.floor(57.2958 * unitHeading) .. ">", 30)
end
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
local dy = (-aZone.rxy) * math.sin(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!!!!
end
@ -3542,8 +3461,101 @@ function cfxZones.startMovingZones()
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

View File

@ -98,10 +98,10 @@ function civAir.readConfigZone()
civAir.aircraftTypes = typeArray
end
if theZone:hasProperty("ups") then
-- if theZone:hasProperty("ups") then
civAir.ups = theZone:getNumberFromZoneProperty("ups", 0.05)
if civAir.ups < .0001 then civAir.ups = 0.05 end
end
-- end
if theZone:hasProperty("maxTraffic") then
civAir.maxTraffic = theZone:getNumberFromZoneProperty( "maxTraffic", 10)
@ -109,15 +109,15 @@ function civAir.readConfigZone()
civAir.maxTraffic = theZone:getNumberFromZoneProperty( "maxFlights", 10)
end
if theZone:hasProperty("maxIdle") then
-- if theZone:hasProperty("maxIdle") then
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)
end
-- end
civAir.verbose = theZone.verbose -- cfxZones.getBoolFromZoneProperty(theZone, "verbose", false)
civAir.verbose = theZone.verbose
end
function civAir.processZone(theZone)

View File

@ -1,5 +1,5 @@
csarManager = {}
csarManager.version = "2.2.6"
csarManager.version = "2.3.1"
csarManager.verbose = false
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.6 - useFlare, now also launches a flare in addition to smoke
- 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
@ -133,6 +141,9 @@ function csarManager.createDownedPilot(theMission, existingUnit)
-- a radius of <1, this is only for ejecting pilots
if newTargetZone.radius > 1 then
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
aLocation.x = newTargetZone.point.x
aLocation.z = newTargetZone.point.z
@ -188,8 +199,10 @@ function csarManager.createCSARMissionData(point, theSide, freq, name, numCrew,
end
end
if not inRadius then inRadius = csarManager.rescueRadius end
newMission.name = "downed " .. name .. "-" .. csarManager.missionID -- make it uuid-capable
newMission.name = 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.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?
@ -468,15 +481,11 @@ function csarManager.heloLanded(theUnit)
"Evacuees",
theMassObject)
msn = theMassObject.ref
-- csarManager.successMission(myName, base.name, msn)
-- to be done when we remove troopsOnBoard
end
-- reset weight
local totalMass = cargoSuper.calculateTotalMassFor(myName)
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
-- we do *not* return so we can pick up troops on
-- a CSARBASE if they were dropped there
@ -521,6 +530,12 @@ function csarManager.heloLanded(theUnit)
30)
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)
table.insert(conf.troopsOnBoard, theMission)
theMission.group:destroy() -- will shut up radio as well
@ -536,16 +551,24 @@ function csarManager.heloLanded(theUnit)
theMassObject)
end
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
-- reset unit's weight based on people on board
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.outText("+++csar: set internal weight for " .. myName .. " to " .. totalMass, 30)
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
@ -747,7 +770,6 @@ function csarManager.doListCSARRequests(args)
local point = theUnit:getPoint()
local theSide = theUnit:getCoalition()
--trigger.action.outText("+++csar: ".. theUnit:getName() .." issued csar status request", 30)
local report = "\nCrews requesting evacuation\n"
local openMissions = csarManager.openMissionsForSide(theSide)
@ -772,11 +794,10 @@ function csarManager.doListCSARRequests(args)
if #myBases < 1 then
report = report .. "\n\nWARNING: NO CSAR BASES TO DELIVER EVACUEES TO"
end
report = report .. "\n"
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
function csarManager.redirectStatusCarrying(args)
@ -788,8 +809,6 @@ function csarManager.doStatusCarrying(args)
local param = args[2]
local theUnit = conf.unit
--trigger.action.outText("+++csar: ".. theUnit:getName() .." wants to know how their rescued troops are doing", 30)
-- build status report
local report = "\nCrew Rescue Status:\n"
if #conf.troopsOnBoard < 1 then
@ -809,7 +828,7 @@ function csarManager.doStatusCarrying(args)
report = report .. "\n"
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
function csarManager.redirectUnloadOne(args)
@ -825,7 +844,7 @@ function csarManager.unloadOne(args)
local report = "NYI: unload one"
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.outSoundForGroup(conf.id, csarManager.actionSound) -- "Quest Snare 3.wav")
return
@ -961,22 +980,16 @@ end
function csarManager.addCSARBase(aZone)
local csarBase = {}
csarBase.zone = aZone
--[[--
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)
-- CSARBASE carries the coalition in the CSARBASE attribute
csarBase.side = aZone:getCoalitionFromZoneProperty("CSARBASE", 0)
-- backward-compatibility to older versions.
-- will be deprecated
if cfxZones.hasProperty(aZone, "coalition") then
csarBase.side = cfxZones.getCoalitionFromZoneProperty(aZone, "CSARBASE", 0)
if aZone:hasProperty("coalition") then
csarBase.side = aZone:getCoalitionFromZoneProperty("CSARBASE", 0)
end
-- 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)
@ -1003,12 +1016,11 @@ function csarManager.getCSARBasesForSide(theSide)
end
return bases
end
--
--
-- U P D A T E
-- ===========
--
--
--
-- 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
if csarManager.useSmoke and (timer.getTime() - csarMission.lastSmokeTime) >= 5 * 60 then
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 )
dcsCommon.markPointWithSmoke(smokePoint, csarManager.smokeColor)
csarMission.lastSmokeTime = timer.getTime()
@ -1120,11 +1132,11 @@ function csarManager.update() -- every second
local ep = evacuee:getPoint()
d = dcsCommon.distFlat(uPoint, ep)
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 oclock = dcsCommon.clockPositionOfARelativeToB(ep, uPoint, ownHeading) .. " o'clock"
-- 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 (agl <= csarManager.hoverAlt) and (agl > 3) then
@ -1137,7 +1149,7 @@ function csarManager.update() -- every second
hoverTime = timer.getTime() - hoverTime -- calculate number of seconds
local remainder = math.floor(csarManager.hoverDuration - hoverTime)
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
-- we rescued the guy!
hoverMsg = "We have " .. csarMission.name .. " safely on board!"
@ -1172,7 +1184,7 @@ function csarManager.update() -- every second
trigger.action.outTextForGroup(uID, hoverMsg, 30, true)
return -- only ever one winch op
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
end
else -- not inside hover dist
@ -1199,12 +1211,17 @@ function csarManager.update() -- every second
-- check if their flag value has changed
if theZone.startCSAR then
-- this should always be true, but you never know
local currVal = cfxZones.getFlagValue(theZone.startCSAR, theZone)
if currVal ~= theZone.lastCSARVal then
-- local currVal = theZone:getFlagValue(theZone.startCSAR)
-- if currVal ~= theZone.lastCSARVal then
if theZone:testZoneFlag(theZone.startCSAR, theZone.triggerMethod, "lastCSARVal") then
-- 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(
mPoint, --cfxZones.getPoint(theZone), -- point
mPoint,
theZone.csarSide, -- theSide
theZone.csarFreq, -- freq
theZone.csarName, -- name
@ -1214,7 +1231,7 @@ function csarManager.update() -- every second
0.1, --theZone.radius) -- radius
nil) -- parashoo unit
csarManager.addMission(theMission)
theZone.lastCSARVal = currVal
--theZone.lastCSARVal = currVal
if csarManager.verbose then
trigger.action.outText("+++csar: started CSAR mission " .. theZone.csarName, 30)
end
@ -1235,23 +1252,9 @@ function csarManager.createCSARforUnit(theUnit, pilotName, radius, silent, score
local coal = theUnit:getCoalition()
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
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
--]]--
local surf = land.getSurfaceType(csarPoint)
csarPoint.y = land.getHeight(csarPoint)
-- 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
local theMission = csarManager.createCSARMissionData(pos, coa, nil, name, nil, nil, nil, 0.1, nil)
csarManager.addMission(theMission)
-- if not silent then
trigger.action.outTextForCoalition(coa, "MAYDAY MAYDAY MAYDAY! ".. name .. " requesting extraction after eject!", 30)
trigger.action.outSoundForGroup(coa, csarManager.actionSound) -- "Quest Snare 3.wav")
-- end
trigger.action.outSoundForGroup(coa, csarManager.actionSound)
end
--
@ -1290,7 +1291,6 @@ end
function csarManager.processCSARBASE()
local csarBases = cfxZones.zonesWithProperty("CSARBASE")
-- now add all zones to my zones table, and init additional info
-- from properties
for k, aZone in pairs(csarBases) do
@ -1305,54 +1305,58 @@ end
function csarManager.readCSARZone(theZone)
-- zones have attribute "CSAR"
-- gather data, and then create a mission from this
local mName = cfxZones.getStringFromZoneProperty(theZone, "CSAR", "Lt. Unknown")
if mName == "" then mName = theZone.name end
local theSide = cfxZones.getCoalitionFromZoneProperty(theZone, "coalition", 0)
local mName = theZone:getStringFromZoneProperty("CSAR", theZone.name)
-- if mName == "" then mName = theZone.name end
local theSide = theZone:getCoalitionFromZoneProperty("coalition", 0)
theZone.csarSide = theSide
theZone.csarName = mName -- now deprecating name attributes
if cfxZones.hasProperty(theZone, "name") then
theZone.csarName = cfxZones.getStringFromZoneProperty(theZone, "name", "<none>")
elseif cfxZones.hasProperty(theZone, "csarName") then
theZone.csarName = cfxZones.getStringFromZoneProperty(theZone, "csarName", "<none>")
elseif cfxZones.hasProperty(theZone, "pilotName") then
theZone.csarName = cfxZones.getStringFromZoneProperty(theZone, "pilotName", "<none>")
elseif cfxZones.hasProperty(theZone, "victimName") then
theZone.csarName = cfxZones.getStringFromZoneProperty(theZone, "victimName", "<none>")
if theZone:hasProperty("name") then
theZone.csarName = theZone:getStringFromZoneProperty("name", "<none>")
elseif theZone:hasProperty("csarName") then
theZone.csarName = theZone:getStringFromZoneProperty("csarName", "<none>")
elseif theZone:hasProperty("pilotName") then
theZone.csarName = theZone:getStringFromZoneProperty("pilotName", "<none>")
elseif theZone:hasProperty("victimName") then
theZone.csarName = theZone:getStringFromZoneProperty("victimName", "<none>")
end
theZone.csarFreq = cfxZones.getNumberFromZoneProperty(theZone, "freq", 0)
theZone.csarFreq = theZone:getNumberFromZoneProperty("freq", 0)
-- since freqs are set in 10kHz multiplier by DML
-- we have to divide the feq given here by 10
theZone.csarFreq = theZone.csarFreq / 10
if theZone.csarFreq < 0.01 then theZone.csarFreq = nil end
theZone.numCrew = 1
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
local deferred = cfxZones.getBoolFromZoneProperty(theZone, "deferred", false)
local deferred = theZone:getBoolFromZoneProperty("deferred", false)
if cfxZones.hasProperty(theZone, "in?") then
theZone.startCSAR = cfxZones.getStringFromZoneProperty(theZone, "in?", "*none")
theZone.lastCSARVal = cfxZones.getFlagValue(theZone.startCSAR, theZone)
if theZone:hasProperty("in?") then
theZone.startCSAR = theZone:getStringFromZoneProperty("in?", "*none")
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
if cfxZones.hasProperty(theZone, "start?") then
theZone.startCSAR = cfxZones.getStringFromZoneProperty(theZone, "start?", "*none")
theZone.lastCSARVal = cfxZones.getFlagValue(theZone.startCSAR, theZone)
end
if cfxZones.hasProperty(theZone, "startCSAR?") then
theZone.startCSAR = cfxZones.getStringFromZoneProperty(theZone, "startCSAR?", "*none")
theZone.lastCSARVal = cfxZones.getFlagValue(theZone.startCSAR, theZone)
end
if cfxZones.hasProperty(theZone, "score") then
theZone.score = cfxZones.getNumberFromZoneProperty(theZone, "score", 100)
if theZone:hasProperty("score") then
theZone.score = theZone:getNumberFromZoneProperty("score", 100)
end
theZone.triggerMethod = theZone:getStringFromZoneProperty("triggerMethod", "change")
theZone.rndLoc = theZone:getBoolFromZoneProperty("rndLoc", true)
theZone.onRoad = theZone:getBoolFromZoneProperty("onRoad", false)
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(
mPoint,
theZone.csarSide,
@ -1379,13 +1383,10 @@ end
function csarManager.processCSARZones()
local csarBases = cfxZones.zonesWithProperty("CSAR")
-- now add all zones to my zones table, and init additional info
-- from properties
for k, aZone in pairs(csarBases) do
csarManager.readCSARZone(aZone)
end
end
@ -1406,6 +1407,7 @@ function csarManager.installCallback(theCB)
end
function csarManager.readConfigZone()
csarManager.name = "csarManagerConfig" -- compat with cfxZones
local theZone = cfxZones.getZoneByName("csarManagerConfig")
if not theZone then
if csarManager.verbose then
@ -1415,48 +1417,46 @@ function csarManager.readConfigZone()
end
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 = cfxZones.getBoolFromZoneProperty(theZone, "useSmoke", true)
csarManager.smokeColor = cfxZones.getSmokeColorStringFromZoneProperty(theZone, "smokeColor", "blue")
csarManager.useSmoke = theZone:getBoolFromZoneProperty("useSmoke", true)
csarManager.smokeColor = theZone:getSmokeColorStringFromZoneProperty("smokeColor", "blue")
csarManager.smokeDist = theZone:getNumberFromZoneProperty("smokeDist", 30)
csarManager.smokeColor = dcsCommon.smokeColor2Num(csarManager.smokeColor)
csarManager.useFlare = cfxZones.getBoolFromZoneProperty(theZone, "useFlare", true)
csarManager.flareColor = cfxZones.getFlareColorStringFromZoneProperty(theZone, "flareColor", "red")
csarManager.useFlare = theZone:getBoolFromZoneProperty("useFlare", true)
csarManager.flareColor = theZone:getFlareColorStringFromZoneProperty("flareColor", "red")
csarManager.flareColor = dcsCommon.flareColor2Num(csarManager.flareColor)
if cfxZones.hasProperty(theZone, "csarRedDelivered!") then
csarManager.csarRedDelivered = cfxZones.getStringFromZoneProperty(theZone, "csarRedDelivered!", "*<none>")
if theZone:hasProperty("csarRedDelivered!") then
csarManager.csarRedDelivered = theZone:getStringFromZoneProperty("csarRedDelivered!", "*<none>")
end
if cfxZones.hasProperty(theZone, "csarBlueDelivered!") then
csarManager.csarBlueDelivered = cfxZones.getStringFromZoneProperty(theZone, "csarBlueDelivered!", "*<none>")
if theZone:hasProperty("csarBlueDelivered!") then
csarManager.csarBlueDelivered = theZone:getStringFromZoneProperty("csarBlueDelivered!", "*<none>")
end
if cfxZones.hasProperty(theZone, "csarDelivered!") then
csarManager.csarDelivered = cfxZones.getStringFromZoneProperty(theZone, "csarDelivered!", "*<none>")
if theZone:hasProperty("csarDelivered!") then
csarManager.csarDelivered = theZone:getStringFromZoneProperty("csarDelivered!", "*<none>")
end
csarManager.rescueRadius = cfxZones.getNumberFromZoneProperty(theZone, "rescueRadius", 70) --70 -- must land within 50m to rescue
csarManager.hoverRadius = cfxZones.getNumberFromZoneProperty(theZone, "hoverRadius", 30) -- 30 -- must hover within 10m of unit
csarManager.hoverAlt = cfxZones.getNumberFromZoneProperty(theZone, "hoverAlt", 40) -- 40 -- must hover below this alt
csarManager.hoverDuration = cfxZones.getNumberFromZoneProperty(theZone, "hoverDuration", 20) -- 20 -- must hover for this duration
csarManager.rescueTriggerRange = cfxZones.getNumberFromZoneProperty(theZone, "rescueTriggerRange", 2000) -- 2000 -- when the unit pops smoke and radios
csarManager.beaconSound = cfxZones.getStringFromZoneProperty(theZone, "beaconSound", "Radio_beacon_of_distress_on_121.ogg") --"Radio_beacon_of_distress_on_121,5_MHz.ogg"
csarManager.pilotWeight = cfxZones.getNumberFromZoneProperty(theZone, "pilotWeight", 120) -- 120
csarManager.rescueRadius = theZone:getNumberFromZoneProperty( "rescueRadius", 70)
csarManager.hoverRadius = theZone:getNumberFromZoneProperty( "hoverRadius", 30)
csarManager.hoverAlt = theZone:getNumberFromZoneProperty("hoverAlt", 40)
csarManager.hoverDuration = theZone:getNumberFromZoneProperty( "hoverDuration", 20)
csarManager.rescueTriggerRange = theZone:getNumberFromZoneProperty("rescueTriggerRange", 2000)
csarManager.beaconSound = theZone:getStringFromZoneProperty( "beaconSound", "Radio_beacon_of_distress_on_121,5_MHz.ogg")
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.vectoring = cfxZones.getBoolFromZoneProperty(theZone, "vectoring", true)
csarManager.actionSound = theZone:getStringFromZoneProperty( "actionSound", "Quest Snare 3.wav")
csarManager.vectoring = theZone:getBoolFromZoneProperty("vectoring", true)
-- add own troop carriers
if cfxZones.hasProperty(theZone, "troopCarriers") then
local tc = cfxZones.getStringFromZoneProperty(theZone, "troopCarriers", "UH-1D")
if theZone:hasProperty("troopCarriers") then
local tc = theZone:getStringFromZoneProperty("troopCarriers", "UH-1D")
tc = dcsCommon.splitString(tc, ",")
csarManager.troopCarriers = dcsCommon.trimArray(tc)
if csarManager.verbose then
@ -1467,6 +1467,8 @@ function csarManager.readConfigZone()
end
end
csarManager.addPrefix = theZone:getBoolFromZoneProperty("addPrefix", true)
if csarManager.verbose then
trigger.action.outText("+++csar: read config", 30)
end
@ -1536,5 +1538,5 @@ end
-- 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.version = "2.9.3"
dcsCommon.version = "2.9.5"
--[[-- VERSION HISTORY
2.2.6 - compassPositionOfARelativeToB
- clockPositionOfARelativeToB
@ -173,6 +173,9 @@ dcsCommon.version = "2.9.3"
- new getCountriesForCoalition()
2.9.2 - updated event2text
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
@ -845,6 +848,13 @@ dcsCommon.version = "2.9.3"
return "North"
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)
local bearing = math.floor(inrad / math.pi * 180)
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
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()
local degrees = math.random(360) * 3.14152 / 180
return degrees

View File

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

View File

@ -111,7 +111,7 @@ function mxObjects.showNClosestTextObjectsToUnit(n, theUnit, numbered)
-- dist
msg = msg .. " " .. dist .. units
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
return msg

View File

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

View File

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

Binary file not shown.