mirror of
https://github.com/weyne85/DML.git
synced 2025-10-29 16:57:49 +00:00
Version 1.4.7
New bombsAway module
This commit is contained in:
parent
08527a515d
commit
c3ba1e7c05
Binary file not shown.
Binary file not shown.
@ -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
385
modules/TDZ.lua
Normal 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
564
modules/bombRange.lua
Normal 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
|
||||
--
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
--]]--
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
BIN
tutorial & demo missions/demo - bombs away.miz
Normal file
BIN
tutorial & demo missions/demo - bombs away.miz
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user