Version 1.2.2

New Valet Module
Wildcard refactoring
Cloner to delicates integration
This commit is contained in:
Christian Franz 2023-01-26 10:31:40 +01:00
parent 43d598f035
commit 654b782894
12 changed files with 1037 additions and 425 deletions

Binary file not shown.

Binary file not shown.

View File

@ -1,5 +1,5 @@
FARPZones = {}
FARPZones.version = "1.2.0"
FARPZones.version = "1.2.1"
FARPZones.verbose = false
--[[--
Version History
@ -13,6 +13,8 @@ FARPZones.verbose = false
- verbose cleanup ("FZ: something happened")
1.2.0 - persistence
- handles contested state
1.2.1 - now gracefully handles a FARP Zone that does not
contain a FARP, but is placed beside it
--]]--
@ -120,9 +122,9 @@ function FARPZones.createFARPFromZone(aZone)
local theFarp = {}
theFarp.zone = aZone
theFarp.name = aZone.name
theFarp.point = cfxZones.getPoint(aZone) -- failsafe
-- find the FARPS that belong to this zone
local thePoint = aZone.point
local thePoint = cfxZones.getPoint(aZone)
local mapFarps = dcsCommon.getAirbasesInRangeOfPoint(
thePoint,
aZone.radius,
@ -132,25 +134,35 @@ function FARPZones.createFARPFromZone(aZone)
theFarp.myFarps = mapFarps
theFarp.owner = 0 -- start with neutral
aZone.owner = 0
if #mapFarps == 0 then
trigger.action.outText("***Farp Zones: no FARP found for zone " .. aZone.name, 30)
else
--for idx, aFarp in pairs(mapFarps) do
-- trigger.action.outText("Associated FARP " .. aFarp:getName() .. " with FARP Zone " .. aZone.name, 30)
--end
if #mapFarps == 0 then
if aZone.verbose or FARPZones.verbose then
trigger.action.outText("***Farp Zones: no FARP found inside zone " .. aZone.name .. ", associating closest FARP", 30)
end
local closest = dcsCommon.getClosestAirbaseTo(thePoint, 1)
if not closest then
trigger.action.outText("***FARP Zones: unable to find a FARP to associate zone <" .. aZone.name .. "> with.", 30)
return
else
if aZone.verbose or FARPZones.verbose then
trigger.action.outText("associated FARP <" .. closest:getName() .. "> with zone <" .. aZone.name .. ">", 30)
end
end
mapFarps[1] = closest
end
theFarp.mainFarp = theFarp.myFarps[1]
theFarp.point = theFarp.mainFarp:getPoint() -- this is FARP, not zone!!!
theFarp.owner = theFarp.mainFarp:getCoalition()
aZone.owner = theFarp.owner
end
theFarp.mainFarp = theFarp.myFarps[1]
theFarp.point = theFarp.mainFarp:getPoint() -- this is FARP, not zone!!!
theFarp.owner = theFarp.mainFarp:getCoalition()
aZone.owner = theFarp.owner
-- end
-- get r and phi for defenders
local rPhi = cfxZones.getVectorFromZoneProperty(
aZone,
"rPhiHDef",
3)
--trigger.action.outText("*** DEF rPhi are " .. rPhi[1] .. " and " .. rPhi[2] .. " heading " .. rPhi[3], 30)
-- get r and phi for facilities
-- create a new defenderzone for this
local r = rPhi[1]

View File

@ -31,6 +31,7 @@ cfxObjectSpawnZones.verbose = false
-- - 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
-- respawn currently happens after theSpawns is deleted and cooldown seconds have passed
@ -123,7 +124,8 @@ function cfxObjectSpawnZones.createSpawner(inZone)
-- see if the spawn can be made brittle/delicte
if cfxZones.hasProperty(inZone, "useDelicates") then
theSpawner.delicateName = cfxZones.getStringFromZoneProperty(inZone, "useDelicates", "<none>")
theSpawner.delicateName = dcsCommon.trim(cfxZones.getStringFromZoneProperty(inZone, "useDelicates", "<none>"))
if theSpawner.delicateName == "*" then theSpawner.delicateName = inZone.name end
end
-- see if it is linked to a ship to set realtive orig headiong

View File

@ -64,6 +64,7 @@ cfxSpawnZones.spawnedGroups = {}
-- 1.7.1 - improved verbosity
-- - spelling check
-- 1.7.2 - baseName now can can be set to zone name by issuing "*"
-- 1.7.3 - ability to hand off to delicates, useDelicates attribute
--
-- new version requires cfxGroundTroops, where they are
--
@ -152,7 +153,14 @@ function cfxSpawnZones.createSpawner(inZone)
if cfxZones.hasProperty(inZone, "trackWith:") then
inZone.trackWith = cfxZones.getStringFromZoneProperty(inZone, "trackWith:", "<None>")
end
-- interface to delicates
if cfxZones.hasProperty(inZone, "useDelicates") then
theSpawner.delicateName = dcsCommon.trim(cfxZones.getStringFromZoneProperty(inZone, "useDelicates", "<none>"))
if theSpawner.delicateName == "*" then theSpawner.delicateName = inZone.name end
end
-- connect with ME if a trigger flag is given
if cfxZones.hasProperty(inZone, "f?") then
theSpawner.triggerFlag = cfxZones.getStringFromZoneProperty(inZone, "f?", "none")
@ -411,6 +419,17 @@ function cfxSpawnZones.spawnWithSpawner(aSpawner)
end
-- hand off to delicates
if aSpawner.delicateName and delicates then
-- pass this object to the delicate zone mentioned
local theDeli = delicates.getDelicatesByName(aSpawner.delicateName)
if theDeli then
delicates.addGroupToInventoryForZone(theDeli, newTroops)
else
trigger.action.outText("+++Spwn: spawner <" .. aZone.name .. "> can't find delicates <" .. aSpawner.delicateName .. ">", 30)
end
end
-- track this if we are have a trackwith attribute
-- note that we retrieve trackwith from ZONE, not spawner
if theZone.trackWith then

View File

@ -1,5 +1,5 @@
cfxZones = {}
cfxZones.version = "3.0.0"
cfxZones.version = "3.0.2"
-- cf/x zone management module
-- reads dcs zones and makes them accessible and mutable
@ -97,7 +97,6 @@ cfxZones.version = "3.0.0"
- isPointInsideZone() returns delta as well
- 2.9.0 - linked zones can useOffset and useHeading
- getPoint update
- new getOrigin()
- pointInZone understands useOrig
- allStaticsInZone supports useOrig
- dPhi for zones with useHeading
@ -117,6 +116,9 @@ cfxZones.version = "3.0.0"
linedUnit and warning.
- initZoneVerbosity()
- 3.0.1 - updateMovingZones() better tracks linked units by name
- 3.0.2 - maxRadius for all zones, only differs from radius in polyZones
- re-factoring zone-base string processing from messenger module
- new processStringWildcards() that does almost all that messenger can
--]]--
@ -244,6 +246,7 @@ function cfxZones.readFromDCS(clearfirst)
-- circular zone
newZone.isCircle = true
newZone.radius = dcsZone.radius
newZone.maxRadius = newZone.radius -- same for circular
elseif zoneType == 2 then
-- polyZone
@ -254,6 +257,7 @@ function cfxZones.readFromDCS(clearfirst)
-- now transfer all point in the poly
-- note: DCS in 2.7 misspells vertices as 'verticies'
-- correct for this
newZone.maxRadius = 0
local verts = {}
if dcsZone.verticies then verts = dcsZone.verticies
else
@ -265,6 +269,10 @@ function cfxZones.readFromDCS(clearfirst)
local dcsPoint = verts[v]
local polyPoint = cfxZones.createPointFromDCSPoint(dcsPoint) -- (x, y) --> (x, 0, y-->z)
newZone.poly[v] = polyPoint
-- measure distance from zone's point, and store maxRadius
-- dcs always saves a point with the poly zone
local dist = dcsCommon.dist(newZone.point, polyPoint)
if dist > newZone.maxRadius then newZone.maxRadius = dist end
end
else
@ -1194,65 +1202,12 @@ function cfxZones.createGroundUnitsInZoneForCoalition (theCoalition, groupName,
return newGroup, groupDataCopy
end
-- parsing zone names. The first part of the name until the first blank " "
-- is the prefix and is dropped unless keepPrefix is true.
-- all others are regarded as key:value pairs and are then added
-- to the zone
-- separated by equal sign "=" AND MUST NOT CONTAIN BLANKS
--
-- example usage "followZone unit=rotary-1 dx=30 dy=25 rotateWithHeading=true
-- ===============
-- FLAG PROCESSING
-- ===============
--
-- OLD DEPRECATED TECH -- TO BE DECOMMISSIONED SOON, DO NOT USE
--
--[[--
function cfxZones.parseZoneNameIntoAttributes(theZone, keepPrefix)
-- trigger.action.outText("Parsing zone: ".. theZone.name, 30)
if not keepPrefix then keepPrefix = false end -- simply for clarity
-- now split the name into space-separated strings
local attributes = dcsCommon.splitString(theZone.name, " ")
if not keepPrefix then table.remove(attributes, 1) end -- pop prefix
-- now parse all substrings and add them as attributes to theZone
for i=1, #attributes do
local a = attributes[i]
local kvp = dcsCommon.splitString(a, "=")
if #kvp == 2 then
-- we have key value pair
local theKey = kvp[1]
local theValue = kvp[2]
theZone[theKey] = theValue
-- trigger.action.outText("Zone ".. theZone.name .. " parsed: Key = " .. theKey .. ", Value = " .. theValue, 30)
else
-- trigger.action.outText("Zone ".. theZone.name .. ": dropped attribute " .. a, 30)
end
end
end
--]]--
-- OLD DEPRECATED TECH -- TO BE DECOMMISSIONED SOON, DO NOT USE
--[[--
function cfxZones.processCraterZones ()
local craters = cfxZones.zonesStartingWith("crater")
-- all these zones need to be processed and their name infor placed into attributes
for cName, cZone in pairs(craters) do
cfxZones.parseZoneNameIntoAttributes(cZone)
-- blow stuff up at the location of the zone
local cPoint = cZone.point
cPoint.y = land.getHeight({x = cPoint.x, y = cPoint.z}) -- compensate for ground level
trigger.action.explosion(cPoint, 900)
-- now interpret and act on the crater info
-- to destroy and place fire.
-- fire has small, medium, large
-- eg. fire=large
end
end
--]]--
--
-- Flag Pulling
--
@ -1538,8 +1493,6 @@ function cfxZones.isMEFlag(inFlag)
trigger.action.outText("+++zne: warning: deprecated isMEFlag", 30)
return true
-- returns true if inFlag is a pure positive number
-- inFlag = dcsCommon.trim(inFlag)
-- return dcsCommon.stringIsPositiveNumber(inFlag)
end
function cfxZones.verifyMethod(theMethod, theZone)
@ -1579,7 +1532,6 @@ function cfxZones.verifyMethod(theMethod, theZone)
local op = string.sub(theMethod, 1, 1)
local remainder = string.sub(theMethod, 2)
remainder = dcsCommon.trim(remainder) -- remove all leading and trailing spaces
-- local rNum = tonumber(remainder)
if true then
-- we have a comparison = ">", "=", "<" followed by a number
@ -1898,7 +1850,9 @@ function cfxZones.flagArrayFromString(inString)
end
--
-- PROPERTY PROCESSING
-- ===================
-- PROPERTY PROCESSING
-- ===================
--
function cfxZones.getAllZoneProperties(theZone, caseInsensitive) -- return as dict
@ -2030,9 +1984,7 @@ function cfxZones.getPositiveRangeFromZoneProperty(theZone, theProperty, default
upperBound = lowerBound
lowerBound = temp
end
-- if rndFlags.verbose then
-- trigger.action.outText("+++Zne: detected range <" .. lowerBound .. ", " .. upperBound .. ">", 30)
-- end
else
-- bounds illegal
trigger.action.outText("+++Zne: illegal range <" .. rangeString .. ">, using " .. default .. "-" .. default, 30)
@ -2043,7 +1995,7 @@ function cfxZones.getPositiveRangeFromZoneProperty(theZone, theProperty, default
upperBound = cfxZones.getNumberFromZoneProperty(theZone, theProperty, default) -- between pulses
lowerBound = upperBound
end
-- trigger.action.outText("+++Zne: returning <" .. lowerBound .. ", " .. upperBound .. ">", 30)
return lowerBound, upperBound
end
@ -2201,7 +2153,290 @@ function cfxZones.getSmokeColorStringFromZoneProperty(theZone, theProperty, defa
end
--
-- Moving Zones. They contain a link to their unit
-- Zone-based wildcard processing
--
-- process <z>
function cfxZones.processZoneStatics(inMsg, theZone)
if theZone then
inMsg = inMsg:gsub("<z>", theZone.name)
end
return inMsg
end
-- process <t>, <lat>, <lon>, <ele>, <mgrs>
function cfxZones.processSimpleZoneDynamics(inMsg, theZone, timeFormat, imperialUnits)
if not inMsg then return "<nil inMsg>" end
-- replace <t> with current mission time HMS
local absSecs = timer.getAbsTime()-- + env.mission.start_time
while absSecs > 86400 do
absSecs = absSecs - 86400 -- subtract out all days
end
if not timeFormat then timeFormat = "<:h>:<:m>:<:s>" end
local timeString = dcsCommon.processHMS(timeFormat, absSecs)
local outMsg = inMsg:gsub("<t>", timeString)
-- replace <lat> with lat of zone point and <lon> with lon of zone point
-- and <mgrs> with mgrs coords of zone point
local currPoint = cfxZones.getPoint(theZone)
local lat, lon = coord.LOtoLL(currPoint)
lat, lon = dcsCommon.latLon2Text(lat, lon)
local alt = land.getHeight({x = currPoint.x, y = currPoint.z})
if imperialUnits then
alt = math.floor(alt * 3.28084) -- feet
else
alt = math.floor(alt) -- meters
end
outMsg = outMsg:gsub("<lat>", lat)
outMsg = outMsg:gsub("<lon>", lon)
outMsg = outMsg:gsub("<ele>", alt)
local grid = coord.LLtoMGRS(coord.LOtoLL(currPoint))
local mgrs = grid.UTMZone .. ' ' .. grid.MGRSDigraph .. ' ' .. grid.Easting .. ' ' .. grid.Northing
outMsg = outMsg:gsub("<mgrs>", mgrs)
return outMsg
end
-- process <v: flag>, <rsp: flag> <rrnd>
function cfxZones.processDynamicValues(inMsg, theZone, msgResponses)
-- replace all occurences of <v: flagName> with their values
local pattern = "<v:%s*[%s%w%*%d%.%-_]+>" -- no list allowed but blanks and * and . and - and _ --> we fail on the other specials to keep this simple
local outMsg = inMsg
repeat -- iterate all patterns one by one
local startLoc, endLoc = string.find(outMsg, pattern)
if startLoc then
local theValParam = string.sub(outMsg, startLoc, endLoc)
-- strip lead and trailer
local param = string.gsub(theValParam, "<v:%s*", "")
param = string.gsub(param, ">","")
-- param = dcsCommon.trim(param) -- trim is called anyway
-- access flag
local val = cfxZones.getFlagValue(param, theZone)
val = tostring(val)
if not val then val = "NULL" end
-- replace pattern in original with new val
outMsg = string.gsub(outMsg, pattern, val, 1) -- only one sub!
end
until not startLoc
-- now process rsp
pattern = "<rsp:%s*[%s%w%*%d%.%-_]+>" -- no list allowed but blanks and * and . and - and _ --> we fail on the other specials to keep this simple
if msgResponses and (#msgResponses > 0) then -- only if this zone has an array
--trigger.action.outText("enter response proccing", 30)
repeat -- iterate all patterns one by one
local startLoc, endLoc = string.find(outMsg, pattern)
if startLoc then
--trigger.action.outText("response: found an occurence", 30)
local theValParam = string.sub(outMsg, startLoc, endLoc)
-- strip lead and trailer
local param = string.gsub(theValParam, "<rsp:%s*", "")
param = string.gsub(param, ">","")
-- access flag
local val = cfxZones.getFlagValue(param, theZone)
if not val or (val < 1) then val = 1 end
if val > msgResponses then val = msgResponses end
val = msgResponses[val]
val = dcsCommon.trim(val)
-- replace pattern in original with new val
outMsg = string.gsub(outMsg, pattern, val, 1) -- only one sub!
end
until not startLoc
-- rnd response
local rndRsp = dcsCommon.pickRandom(msgResponses)
outMsg = outMsg:gsub ("<rrnd>", rndRsp)
end
return outMsg
end
-- process <t: flag>
function cfxZones.processDynamicTime(inMsg, theZone, timeFormat)
if not timeFormat then timeFormat = "<:h>:<:m>:<:s>" end
-- replace all occurences of <t: flagName> with their values
local pattern = "<t:%s*[%s%w%*%d%.%-_]+>" -- no list allowed but blanks and * and . and - and _ --> we fail on the other specials to keep this simple
local outMsg = inMsg
repeat -- iterate all patterns one by one
local startLoc, endLoc = string.find(outMsg, pattern)
if startLoc then
local theValParam = string.sub(outMsg, startLoc, endLoc)
-- strip lead and trailer
local param = string.gsub(theValParam, "<t:%s*", "")
param = string.gsub(param, ">","")
-- access flag
local val = cfxZones.getFlagValue(param, theZone)
-- use this to process as time value
--trigger.action.outText("time: accessing <" .. param .. "> and received <" .. val .. ">", 30)
local timeString = dcsCommon.processHMS(timeFormat, val)
if not timeString then timeString = "NULL" end
-- replace pattern in original with new val
outMsg = string.gsub(outMsg, pattern, timeString, 1) -- only one sub!
end
until not startLoc
return outMsg
end
-- process <lat/lon/ele/mgrs/lle/latlon/alt/vel/hdg/rhdg/type/player: zone/unit>
function cfxZones.processDynamicLoc(inMsg, imperialUnits, responses)
local locales = {"lat", "lon", "ele", "mgrs", "lle", "latlon", "alt", "vel", "hdg", "rhdg", "type", "player"}
local outMsg = inMsg
local uHead = 0
for idx, aLocale in pairs(locales) do
local pattern = "<" .. aLocale .. ":%s*[%s%w%*%d%.%-_]+>"
repeat -- iterate all patterns one by one
local startLoc, endLoc = string.find(outMsg, pattern)
if startLoc then
local theValParam = string.sub(outMsg, startLoc, endLoc)
-- strip lead and trailer
local param = string.gsub(theValParam, "<" .. aLocale .. ":%s*", "")
param = string.gsub(param, ">","")
-- find zone or unit
param = dcsCommon.trim(param)
local thePoint = nil
local tZone = cfxZones.getZoneByName(param)
local tUnit = Unit.getByName(param)
local spd = 0
local angels = 0
local theType = "<errType>"
local playerName = "Unknown"
if tZone then
theType = "Zone"
playerName = "?zone?"
thePoint = cfxZones.getPoint(tZone)
if tZone.linkedUnit and Unit.isExist(tZone.linkedUnit) then
local lU = tZone.linkedUnit
local masterPoint = lU:getPoint()
thePoint.y = masterPoint.y
spd = dcsCommon.getUnitSpeed(lU)
spd = math.floor(spd * 3.6)
uHead = math.floor(dcsCommon.getUnitHeading(tUnit) * 57.2958) -- to degrees.
else
-- since zones always have elevation of 0,
-- now get the elevation from the map
thePoint.y = land.getHeight({x = thePoint.x, y = thePoint.z})
end
elseif tUnit then
if Unit.isExist(tUnit) then
theType = tUnit:getTypeName()
if tUnit.getPlayerName and tUnit:getPlayerName() then
playerName = tUnit:getPlayerName()
end
thePoint = tUnit:getPoint()
spd = dcsCommon.getUnitSpeed(tUnit)
-- convert m/s to km/h
spd = math.floor(spd * 3.6)
uHead = math.floor(dcsCommon.getUnitHeading(tUnit) * 57.2958) -- to degrees.
end
else
-- nothing to do, remove me.
end
local locString = "err"
if thePoint then
-- now that we have a point, we can do locale-specific
-- processing. return result in locString
local lat, lon, alt = coord.LOtoLL(thePoint)
lat, lon = dcsCommon.latLon2Text(lat, lon)
angels = math.floor(thePoint.y)
if imperialUnits then
alt = math.floor(alt * 3.28084) -- feet
spd = math.floor(spd * 0.539957) -- km/h to knots
angels = math.floor(angels * 3.28084)
else
alt = math.floor(alt) -- meters
end
if angels > 1000 then
angels = math.floor(angels / 100) * 100
end
if aLocale == "lat" then locString = lat
elseif aLocale == "lon" then locString = lon
elseif aLocale == "ele" then locString = tostring(alt)
elseif aLocale == "lle" then locString = lat .. " " .. lon .. " ele " .. tostring(alt)
elseif aLocale == "latlon" then locString = lat .. " " .. lon
elseif aLocale == "alt" then locString = tostring(angels) -- don't confuse alt and angels, bad var naming here
elseif aLocale == "vel" then locString = tostring(spd)
elseif aLocale == "hdg" then locString = tostring(uHead)
elseif aLocale == "type" then locString = theType
elseif aLocale == "player" then locString = playerName
elseif aLocale == "rhdg" and (responses) then
local offset = cfxZones.rspMapper360(uHead, #responses)
locString = dcsCommon.trim(responses[offset])
else
-- we have mgrs
local grid = coord.LLtoMGRS(coord.LOtoLL(thePoint))
locString = grid.UTMZone .. ' ' .. grid.MGRSDigraph .. ' ' .. grid.Easting .. ' ' .. grid.Northing
end
end
-- replace pattern in original with new val
outMsg = string.gsub(outMsg, pattern, locString, 1) -- only one sub!
end -- if startloc
until not startLoc
end -- for all locales
return outMsg
end
function cfxZones.rspMapper360(directionInDegrees, numResponses)
-- maps responses around a clock. Clock has 12 'responses' (12, 1, .., 11),
-- with the first (12) also mapping to the last half arc
-- this method dynamically 'winds' the responses around
-- a clock and returns the index of the message to display
if numResponses < 1 then numResponses = 1 end
directionInDegrees = math.floor(directionInDegrees)
while directionInDegrees < 0 do directionInDegrees = directionInDegrees + 360 end
while directionInDegrees >= 360 do directionInDegrees = directionInDegrees - 360 end
-- now we have 0..360
-- calculate arc per item
local arcPerItem = 360 / numResponses
local halfArc = arcPerItem / 2
-- we now map 0..360 to (0-halfArc..360-halfArc) by shifting
-- direction by half-arc and clipping back 0..360
-- and now we can directly derive the index of the response
directionInDegrees = directionInDegrees + halfArc
if directionInDegrees >= 360 then directionInDegrees = directionInDegrees - 360 end
local index = math.floor(directionInDegrees / arcPerItem) + 1 -- 1 .. numResponses
return index
end
-- replaces dcsCommon with same name
-- timeFormat is optional, default is "<:h>:<:m>:<:s>"
-- imperialUnits is optional, defaults to meters
-- responses is an array of string, defaults to {}
function cfxZones.processStringWildcards(inMsg, theZone, timeFormat, imperialUnits, responses)
if not inMsg then return "<nil inMsg>" end
local formerType = type(inMsg)
if formerType ~= "string" then inMsg = tostring(inMsg) end
if not inMsg then inMsg = "<inMsg is incompatible type " .. formerType .. ">" end
local theMsg = inMsg
-- process common DCS stuff like /n
theMsg = dcsCommon.processStringWildcards(theMsg) -- call old inherited
-- process <z>
theMsg = cfxZones.processZoneStatics(theMsg, theZone)
-- process <t>, <lat>, <lon>, <ele>, <mgrs>
theMsg = cfxZones.processSimpleZoneDynamics(theMsg, theZone, timeFormat, imperialUnits)
-- process <v: flag>, <rsp: flag> <rrnd>
theMsg = cfxZones.processDynamicValues(theMsg, theZone, responses)
-- process <t: flag>
theMsg = cfxZones.processDynamicTime(theMsg, theZone, timeFormat)
-- process <lat/lon/ele/mgrs/lle/latlon/alt/vel/hdg/rhdg/type/player: zone/unit>
theMsg = cfxZones.processDynamicLoc(theMsg, imperialUnits, responses)
return theMsg
end
--
-- ============
-- MOVING ZONES
-- ============
--
-- Moving zones contain a link to their unit
-- they are always located at an offset (x,z) or delta, phi
-- to their master unit. delta phi allows adjustment for heading
-- The cool thing about moving zones in cfx is that they do not
@ -2239,13 +2474,7 @@ function cfxZones.getPoint(aZone) -- always works, even linked, returned point c
thePos.x = aZone.point.x
thePos.y = 0 -- aZone.y
thePos.z = aZone.point.z
--[[--
if aZone.linkedUnit then
trigger.action.outText("GetPoint: LINKED <".. aZone.name .. "> p = " .. dcsCommon.point2text(thePos) .. ", O = " .. dcsCommon.point2text(cfxZones.getDCSOrigin(aZone)), 30 )
else
trigger.action.outText("GetPoint: unlinked <".. aZone.name .. "> p = " .. dcsCommon.point2text(thePos) .. ", O = " .. dcsCommon.point2text(cfxZones.getDCSOrigin(aZone)), 30 )
end
--]]--
return thePos
end
@ -2259,8 +2488,7 @@ function cfxZones.linkUnitToZone(theUnit, theZone, dx, dy) -- note: dy is really
local unitHeading = dcsCommon.getUnitHeading(theUnit)
local bearingOffset = math.atan2(dy, dx) -- rads
if bearingOffset < 0 then bearingOffset = bearingOffset + 2 * 3.141592 end
--trigger.action.outText("zone <" .. theZone.name .. "> is <" .. math.floor(bearingOffset * 57.2958) .. "> degrees from Unit <" .. theUnit:getName() .. ">", 30)
--trigger.action.outText("Unit <" .. theUnit:getName() .. "> has heading .. <" .. math.floor(57.2958 * unitHeading) .. ">", 30)
local dPhi = bearingOffset - unitHeading
if dPhi < 0 then dPhi = dPhi + 2 * 3.141592 end
if (theZone.verbose and theZone.useHeading) then
@ -2349,14 +2577,12 @@ function cfxZones.updateMovingZones()
end
function cfxZones.initLink(theZone)
--trigger.action.outText("enter initlink for <" .. theZone.name .. ">")
--if true then return end
--trigger.action.outText("entry verbose check: <" .. theZone.name .. "> is verbose = " .. dcsCommon.bool2YesNo(theZone.verbose), 30)
theZone.linkBroken = true
theZone.linkedUnit = nil
theUnit = Unit.getByName(theZone.linkName)
if theUnit then
--trigger.action.outText("initlink has link to <" .. theZone.linkName .. "> for <" .. theZone.name .. ">", 30)
local dx = 0
local dz = 0
if theZone.useOffset or theZone.useHeading then
@ -2367,12 +2593,12 @@ function cfxZones.initLink(theZone)
dz = delta.z
end
cfxZones.linkUnitToZone(theUnit, theZone, dx, dz) -- also sets theZone.linkedUnit
--trigger.action.outText("verbose check: <" .. theZone.name .. "> is verbose = " .. dcsCommon.bool2YesNo(theZone.verbose), 30)
if theZone.verbose then
trigger.action.outText("Link established for zone <" .. theZone.name .. "> to unit <" .. theZone.linkName .. ">: dx=<" .. math.floor(dx) .. ">, dz=<" .. math.floor(dz) .. "> dist = <" .. math.floor(math.sqrt(dx * dx + dz * dz)) .. ">" , 30)
end
theZone.linkBroken = nil
--trigger.action.outText("done linking <" .. theZone.linkName .. "> to zone <" .. theZone.name .. ">", 30)
else
if theZone.verbose then
trigger.action.outText("Linked unit: no unit <" .. theZone.linkName .. "> to link <" .. theZone.name .. "> to", 30)
@ -2417,26 +2643,25 @@ function cfxZones.startMovingZones()
end
end
--
-- ===========
-- INIT MODULE
-- ===========
--
function cfxZones.initZoneVerbosity()
for aName,aZone in pairs(cfxZones.zones) do
-- support for zone-local verbose flag
aZone.verbose = cfxZones.getBoolFromZoneProperty(aZone, "verbose", false)
end
end
--
-- init
--
function cfxZones.init()
-- read all zones into my own db
cfxZones.readFromDCS(true) -- true: erase old
-- now, pre-read zone owner for all zones
-- note, all zones with this property are by definition owned zones.
-- and hence will be read anyway. this will merely ensure that the
-- ownership is established right away
-- unless owned zones module is missing, in which case
-- ownership is still established
-- pre-read zone owner for all zones
local pZones = cfxZones.zonesWithProperty("owner")
for n, aZone in pairs(pZones) do
aZone.owner = cfxZones.getCoalitionFromZoneProperty(aZone, "owner", 0)

View File

@ -1,5 +1,5 @@
cloneZones = {}
cloneZones.version = "1.7.0"
cloneZones.version = "1.7.1"
cloneZones.verbose = false
cloneZones.requiredLibs = {
"dcsCommon", -- always
@ -91,6 +91,8 @@ cloneZones.respawnOnGroupID = true
- <uid>, <lcl>, <i>, <g> wildcards
- identical=true overrides nameScheme
- masterOwner "*" convenience shortcut
1.7.1 - useDelicates handOff for delicates
- forcedRespawn passes zone instead of verbose
--]]--
@ -337,6 +339,15 @@ function cloneZones.createClonerWithZone(theZone) -- has "Cloner"
--trigger.action.outText("trackwith: " .. theZone.trackWith, 30)
end
-- interface to delicates
if cfxZones.hasProperty(theZone, "useDelicates") then
theZone.delicateName = dcsCommon.trim(cfxZones.getStringFromZoneProperty(theZone, "useDelicates", "<none>"))
if theZone.delicateName == "*" then theZone.delicateName = theZone.name end
if theZone.verbose then
trigger.action.outText("+++clnZ: cloner <" .. theZone.name .."> hands off delicates to <" .. theZone.delicateName .. ">", 30)
end
end
-- randomized locations on spawn
theZone.rndLoc = cfxZones.getBoolFromZoneProperty(theZone, "randomizedLoc", false)
if cfxZones.hasProperty(theZone, "rndLoc") then
@ -998,11 +1009,14 @@ function cloneZones.validateSpawnGroupData(theData, theZone, groupNames, unitNam
end
end
-- forcedRespan respawns a group when the previous spawn of a
-- group did not match the ID that it was supposed to match
function cloneZones.forcedRespawn(args)
local theData = args[1]
local spawnedGroups = args[2]
local pos = args[3]
local verbose = args[4]
local theZone = args[4]
local verbose = theZone.verbose
local rawData = dcsCommon.clone(theData)
if verbose then
trigger.action.outText("clnZ: enter forced respawn of <" .. theData.name .. "> to meet ID " .. theData.CZTargetID .. " (currently set for <" .. theData.groupId .. ">)", 30)
@ -1021,6 +1035,21 @@ function cloneZones.forcedRespawn(args)
trigger.action.outText("will replace table entry at <" .. pos .. "> with new group", 30)
end
spawnedGroups[pos] = theGroup
-- since we are now successful, check if we need to apply
-- delicate status
if theZone.delicateName and delicates then
-- pass this object to the delicate zone mentioned
local theDeli = delicates.getDelicatesByName(theZone.delicateName)
if theDeli then
delicates.addGroupToInventoryForZone(theDeli, theGroup)
else
trigger.action.outText("+++clnZ: spawner <" .. theZone.name .. "> can't find delicates zone <" .. theZone.delicateName .. ">", 30)
end
elseif theZone.delicateName then
trigger.action.outText("+++clnZ: WARNING - cloner <> requires 'Delicates' module.", 30)
end
else
-- we need to try again in one second
if verbose then
@ -1200,6 +1229,7 @@ function cloneZones.spawnWithTemplateForZone(theZone, spawnZone)
-- now spawn all raw data
local groupCollector = {} -- to detect cross-group conflicts
local unitCollector = {} -- to detect cross-group conflicts
local theGroup = nil -- init to empty, on this level
for idx, rawData in pairs (dataToSpawn) do
-- now spawn and save to clones
-- first norm and clone data for later save
@ -1225,7 +1255,7 @@ function cloneZones.spawnWithTemplateForZone(theZone, spawnZone)
end
-- SPAWN NOW!!!!
local theGroup = coalition.addGroup(rawData.CZctry, rawData.CZtheCat, rawData)
theGroup = coalition.addGroup(rawData.CZctry, rawData.CZtheCat, rawData)
table.insert(spawnedGroups, theGroup)
-- update groupXlate table from spawned group
@ -1261,28 +1291,49 @@ function cloneZones.spawnWithTemplateForZone(theZone, spawnZone)
end
end
-- check if our assigned ID matches the handed out by
-- check if our assigned ID matches the one handed out by
-- DCS. Mismatches can happen, and are only noted
if newGroupID == rawData.CZTargetID then
-- we are good
-- we are good, all processing correct
-- add to delicates if set
if spawnZone.delicateName and delicates then
-- pass this object to the delicate zone mentioned
local theDeli = delicates.getDelicatesByName(spawnZone.delicateName)
if theDeli then
delicates.addGroupToInventoryForZone(theDeli, theGroup)
else
trigger.action.outText("+++clnZ: spawner <" .. spawnZone.name .. "> can't find delicates zone <" .. spawnZone.delicateName .. ">", 30)
end
end
else
if cloneZones.verbose or spawnZone.verbose then
trigger.action.outText("clnZ: Note: GROUP ID spawn changed for <" .. rawData.name .. ">: target ID " .. rawData.CZTargetID .. " (target) returns " .. newGroupID .. " (actual) in <" .. spawnZone.name .. ">", 30)
--trigger.action.outText("Note: theData.groupId is <" .. theData.groupId .. ">", 30)
--if spawnZone.identical then
-- trigger.action.outText("(Identical = true detected for this zone)", 30)
--end
end
if cloneZones.respawnOnGroupID then
-- remove last entry in table, will be added later
-- remember pos in table, will be changed after
-- respawn
local pos = #spawnedGroups
timer.scheduleFunction(cloneZones.forcedRespawn, {theData, spawnedGroups, pos, spawnZone.verbose}, timer.getTime() + 2) -- initial gap: 2 seconds for DCS to sort itself out
timer.scheduleFunction(cloneZones.forcedRespawn, {theData, spawnedGroups, pos, spawnZone}, timer.getTime() + 2) -- initial gap: 2 seconds for DCS to sort itself out
-- note that this can in extreme cases result in
-- unitID mismatches, but his is extremely unlikely
else
-- we note it in the spawn data for the group so
-- persistence works fine
theData.groupId = newGroupID
-- since we keep these, we make them brittle if required
if spawnZone.delicateName and delicates then
-- pass this object to the delicate zone mentioned
local theDeli = delicates.getDelicatesByName(spawnZone.delicateName)
if theDeli then
delicates.addGroupToInventoryForZone(theDeli, theGroup)
else
trigger.action.outText("+++clnZ: spawner <" .. spawnZone.name .. "> can't find delicates zone <" .. spawnZone.delicateName .. ">", 30)
end
end
end
end
@ -1387,8 +1438,20 @@ function cloneZones.spawnWithTemplateForZone(theZone, spawnZone)
cloneZones.invokeCallbacks(theZone, "did spawn static", theStatic)
if cloneZones.verbose or spawnZone.verbose then
trigger.action.outText("Static spawn: spawned " .. aStaticName, 30)
trigger.action.outText("+++clnZ: new Static clone " .. aStaticName, 30)
end
-- processing for delicates
if spawnZone.delicateName and delicates then
-- pass this object to the delicate zone mentioned
local theDeli = delicates.getDelicatesByName(spawnZone.delicateName)
if theDeli then
delicates.addStaticObjectToInventoryForZone(theDeli, theStatic)
else
trigger.action.outText("+++cnlZ: cloner <" .. aZone.name .. "> can't find delicates <" .. spawnZone.delicateName .. ">", 30)
end
end
-- processing for cargoManager
if isCargo then
if cfxCargoManager then

View File

@ -1,5 +1,5 @@
dcsCommon = {}
dcsCommon.version = "2.8.0"
dcsCommon.version = "2.8.1"
--[[-- VERSION HISTORY
2.2.6 - compassPositionOfARelativeToB
- clockPositionOfARelativeToB
@ -123,7 +123,10 @@ dcsCommon.version = "2.8.0"
- new getGroupNameByID
- bool2YesNo alsco can return NIL
- new getUnitStartPosByID
2.8.1 - arrayContainsString: type checking for theArray and warning
- processStringWildcards()
- new wildArrayContainsString()
- fix for stringStartsWith oddity with aircraft types
--]]--
-- dcsCommon is a library of common lua functions
@ -1995,9 +1998,56 @@ end
--
--
-- as arrayContainsString, except it includes wildcard matches if EITHER
-- ends on "*"
function dcsCommon.wildArrayContainsString(theArray, theString, caseSensitive)
if not theArray then return false end
if not theString then return false end
if not caseSensitive then caseSensitive = false end
if type(theArray) ~= "table" then
trigger.action.outText("***arrayContainsString: theArray is not type table but <" .. type(theArray) .. ">", 30)
end
if not caseSensitive then theString = string.upper(theString) end
--trigger.action.outText("wildACS: theString = <" .. theString .. ">, theArray contains <" .. #theArray .. "> elements", 30)
local wildIn = dcsCommon.stringEndsWith(theString, "*")
if wildIn then dcsCommon.removeEnding(thestring, "*") end
for i = 1, #theArray do
local theElement = theArray[i]
if caseSensitive then theElement = string.upper(theElement) end
local wildEle = dcsCommon.stringEndsWith(theElement, "*")
if wildEle then theElement = dcsCommon.removeEnding(theElement, "*") end
--trigger.action.outText("matching s=<" .. theString .. "> with e=<" .. theElement .. ">", 30)
if wildEle and wildIn then
-- both end on wildcards, partial match for both
if dcsCommon.stringStartsWith(theElement. theString) then return true end
if dcsCommon.stringStartsWith(theString, theElement) then return true end
--trigger.action.outText("match e* with s* failed.", 30)
elseif wildEle then
-- Element is a wildcard, partial match
if dcsCommon.stringStartsWith(theString, theElement) then return true end
--trigger.action.outText("match e* with s failed.", 30)
elseif wildIn then
-- theString is a wildcard. partial match
if dcsCommon.stringStartsWith(theElement. theString) then return true end
--trigger.action.outText("match e with s* failed.", 30)
else
-- standard: no wildcards, full match
if theArray[i] == theString then return true end
--trigger.action.outText("match e with s (straight) failed.", 30)
end
end
return false
end
function dcsCommon.arrayContainsString(theArray, theString)
if not theArray then return false end
if not theString then return false end
if type(theArray) ~= "table" then
trigger.action.outText("***arrayContainsString: theArray is not type table but <" .. type(theArray) .. ">", 30)
end
for i = 1, #theArray do
if theArray[i] == theString then return true end
end
@ -2117,7 +2167,35 @@ end
function dcsCommon.stringStartsWith(theString, thePrefix)
if not theString then return false end
return theString:find(thePrefix) == 1
if not thePrefix then return false end
-- new code because old 'string.find' had some really
-- strange results with aircraft types. Prefix "A-10" did not
-- match string "A-10A" etc.
local pl = string.len(thePrefix)
if pl > string.len(theString) then return false end
if pl < 1 then return false end
for i=1, pl do
local left = string.sub(theString, i, i)
local right = string.sub(thePrefix, i, i)
if left ~= right then
return false
end
end
return true
--[[-- trigger.action.outText("---- OK???", 30)
-- strange stuff happening with some strings, let's investigate
local res = string.find(theString, thePrefix) == 1
if res then
trigger.action.outText("startswith: <" .. theString .. "> pre <" .. thePrefix .. "> --> YES", 30)
else
trigger.action.outText("startswith: <" .. theString .. "> nojoy pre <" .. thePrefix .. ">", 30)
end
return res
--]]--
end
function dcsCommon.removePrefix(theString, thePrefix)
@ -3008,6 +3086,23 @@ function dcsCommon.LSR(a, num)
return a
end
--
-- string windcards
--
function dcsCommon.processStringWildcards(inMsg)
-- Replace STATIC bits of message like CR and zone name
if not inMsg then return "<nil inMsg>" end
local formerType = type(inMsg)
if formerType ~= "string" then inMsg = tostring(inMsg) end
if not inMsg then inMsg = "<inMsg is incompatible type " .. formerType .. ">" end
local outMsg = ""
-- replace line feeds
outMsg = inMsg:gsub("<n>", "\n")
return outMsg
end
--
-- SEMAPHORES
--

View File

@ -1,5 +1,5 @@
delicates = {}
delicates.version = "1.1.0"
delicates.version = "1.1.1"
delicates.verbose = false
delicates.ups = 1
delicates.requiredLibs = {
@ -16,7 +16,8 @@ delicates.inventory = {}
- addStaticObjectInventoryForZone
- blowAll?
- safetyMargin - safety margin. defaults to 10%
1.1.1 - addGroupToInventoryForZone
- verbose for zone will show update event from useDelicates
--]]--
function delicates.adddDelicates(theZone)
@ -73,9 +74,10 @@ function delicates.makeZoneInventory(theZone)
for idy, anObject in pairs(collector) do
local oName = anObject:getName()
if type(oName) == 'number' then oName = tostring(oName) end
local oLife = anObject:getLife() - anObject:getLife() * theZone.safetyMargin
local mLife = anObject:getLife()
local oLife = mLife - mLife * theZone.safetyMargin
if theZone.verbose or delicates.verbose then
trigger.action.outText("+++deli: cat=".. aCat .. ":<" .. oName .. "> Life=" .. oLife, 30)
trigger.action.outText("+++deli: cat=".. aCat .. ":<" .. oName .. "> explodes when under " .. oLife .. " of max " .. mLife, 30)
end
local uP = anObject:getPoint()
if cfxZones.isPointInsideZone(uP, theZone) then
@ -83,6 +85,7 @@ function delicates.makeZoneInventory(theZone)
local desc = {}
desc.cat = aCat
desc.oLife = oLife
desc.mLife = mLife
desc.theZone = theZone
desc.oName = oName
delicates.inventory[oName] = desc
@ -102,17 +105,34 @@ function delicates.addStaticObjectToInventoryForZone(theZone, theStatic)
local desc = {}
desc.cat = theStatic:getCategory()
desc.mLife = theStatic:getLife()
desc.oLife = theStatic:getLife() - theStatic:getLife() * theZone.safetyMargin
if desc.oLife < 0 then desc.oLife = 0 end
desc.theZone = theZone
desc.oName = theStatic:getName()
if theZone.verbose and delicates[desc.oName] then
trigger.action.outText("+++deli: updating existing delicate <" .. desc.oName .. "> with data from zone <" .. theZone.name .. ">", 30)
end
delicates.inventory[desc.oName] = desc
if theZone.verbose or delicates.verbose then
trigger.action.outText("+++deli: added static <" .. desc.oName .. "> to <" .. theZone.name .. "> with minimal life = <" .. desc.oLife .. "/" .. theStatic:getLife() .. "> = safety margin of " .. theZone.safetyMargin * 100 .. "%", 30)
trigger.action.outText("+++deli: added <" .. desc.oName .. "> to <" .. theZone.name .. "> blows below life = <" .. desc.oLife .. "> of <" .. desc.mLife .. "> = safety margin " .. theZone.safetyMargin * 100 .. "%", 30)
end
end
function delicates.addGroupToInventoryForZone(theZone, theGroup)
-- trigger.action.outText("enter addGroupToInventoryForZone", 30)
if not theZone then return end
if not theGroup then return end
-- trigger.action.outText("before itering addGroupToInventoryForZone", 30)
local allUnits = theGroup:getUnits() -- warning: we assume all alive
for idx, aUnit in pairs (allUnits) do
-- we use 'addStatic' as it also supports units
delicates.addStaticObjectToInventoryForZone(theZone, aUnit)
end
end
function delicates.createDelicatesWithZone(theZone)
theZone.power = cfxZones.getNumberFromZoneProperty(theZone, "power", 10)
@ -221,14 +241,14 @@ function delicates:onEvent(theEvent)
local cLife = theObj:getLife()
if cLife < desc.oLife then
if desc.theZone.verbose or delicates.verbose then
trigger.action.outText("+++deli: BRITTLE TRIGGER: life <" .. cLife .. "> below safety margin <" .. oDesc.oLife .. ">", 30)
trigger.action.outText("+++deli: BRITTLE TRIGGER: life <" .. cLife .. "> below safety margin <" .. desc.oLife .. ">", 30)
end
delicates.blowUpObject(desc)
-- remove it from further searches
delicates.inventory[oName] = nil
else
if desc.theZone.verbose or delicates.verbose then
trigger.action.outText("+++deli: CLOSE CALL, but life <" .. cLife .. "> within safety margin <" .. oDesc.oLife .. ">", 30)
trigger.action.outText("+++deli: CLOSE CALL, but life <" .. cLife .. "> within safety margin <" .. desc.oLife .. ">", 30)
end
end
end
@ -287,6 +307,9 @@ function delicates.update()
if theObj then
local cLife = theObj:getLife()
if cLife < oDesc.mLife and cLife >= oDesc.oLife and oDesc.theZone.verbose then
trigger.action.outText("+++Deli: <" .. oName .. "> was hit, <" .. cLife .. "> still above trigger <" .. oDesc.oLife .. ">", 30)
end
if cLife >= oDesc.oLife then
-- all well, transfer to next iter
newInventory[oName] = oDesc

View File

@ -1,5 +1,5 @@
messenger = {}
messenger.version = "2.1.0"
messenger.version = "2.2.0"
messenger.verbose = false
messenger.requiredLibs = {
"dcsCommon", -- always
@ -61,6 +61,9 @@ messenger.messengers = {}
- fix to messageMute
- <type: unit>
2.1.1 - cosmetic: only output text if len>0 and not cls
2.2.0 - <player: unit>
- made dynamic string gen more portable in prep for move to cfxZones
- refactoring wildcard processing: moved to cfxZones
--]]--
@ -79,268 +82,12 @@ function messenger.getMessengerByName(aName)
return nil
end
--
-- read attributes
--
function messenger.preProcMessage(inMsg, theZone)
-- Replace STATIC bits of message like CR and zone name
if not inMsg then return "<nil inMsg>" end
local formerType = type(inMsg)
if formerType ~= "string" then inMsg = tostring(inMsg) end
if not inMsg then inMsg = "<inMsg is incompatible type " .. formerType .. ">" end
local outMsg = ""
-- replace line feeds
outMsg = inMsg:gsub("<n>", "\n")
if theZone then
outMsg = outMsg:gsub("<z>", theZone.name)
end
return outMsg
end
-- Old-school processing to replace wildcards
-- repalce <t> with current time
-- replace <lat> with zone's current lonlat
-- replace <mgrs> with zone's current mgrs
function messenger.dynamicProcessClassic(inMsg, theZone)
if not inMsg then return "<nil inMsg>" end
-- replace <t> with current mission time HMS
local absSecs = timer.getAbsTime()-- + env.mission.start_time
while absSecs > 86400 do
absSecs = absSecs - 86400 -- subtract out all days
end
local timeString = dcsCommon.processHMS(theZone.msgTimeFormat, absSecs)
local outMsg = inMsg:gsub("<t>", timeString)
-- replace <lat> with lat of zone point and <lon> with lon of zone point
-- and <mgrs> with mgrs coords of zone point
local currPoint = cfxZones.getPoint(theZone)
local lat, lon = coord.LOtoLL(currPoint)
lat, lon = dcsCommon.latLon2Text(lat, lon)
local alt = land.getHeight({x = currPoint.x, y = currPoint.z})
if theZone.imperialUnits then
alt = math.floor(alt * 3.28084) -- feet
else
alt = math.floor(alt) -- meters
end
outMsg = outMsg:gsub("<lat>", lat)
outMsg = outMsg:gsub("<lon>", lon)
outMsg = outMsg:gsub("<ele>", alt)
local grid = coord.LLtoMGRS(coord.LOtoLL(currPoint))
local mgrs = grid.UTMZone .. ' ' .. grid.MGRSDigraph .. ' ' .. grid.Easting .. ' ' .. grid.Northing
outMsg = outMsg:gsub("<mgrs>", mgrs)
return outMsg
end
--
-- new dynamic flag processing
--
function messenger.processDynamicValues(inMsg, theZone)
-- replace all occurences of <v: flagName> with their values
local pattern = "<v:%s*[%s%w%*%d%.%-_]+>" -- no list allowed but blanks and * and . and - and _ --> we fail on the other specials to keep this simple
local outMsg = inMsg
repeat -- iterate all patterns one by one
local startLoc, endLoc = string.find(outMsg, pattern)
if startLoc then
local theValParam = string.sub(outMsg, startLoc, endLoc)
-- strip lead and trailer
local param = string.gsub(theValParam, "<v:%s*", "")
param = string.gsub(param, ">","")
-- param = dcsCommon.trim(param) -- trim is called anyway
-- access flag
local val = cfxZones.getFlagValue(param, theZone)
val = tostring(val)
if not val then val = "NULL" end
-- replace pattern in original with new val
outMsg = string.gsub(outMsg, pattern, val, 1) -- only one sub!
end
until not startLoc
-- now process rsp
pattern = "<rsp:%s*[%s%w%*%d%.%-_]+>" -- no list allowed but blanks and * and . and - and _ --> we fail on the other specials to keep this simple
if theZone.msgResponses and (#theZone.msgResponses > 0) then -- only if this zone has an array
--trigger.action.outText("enter response proccing", 30)
repeat -- iterate all patterns one by one
local startLoc, endLoc = string.find(outMsg, pattern)
if startLoc then
--trigger.action.outText("response: found an occurence", 30)
local theValParam = string.sub(outMsg, startLoc, endLoc)
-- strip lead and trailer
local param = string.gsub(theValParam, "<rsp:%s*", "")
param = string.gsub(param, ">","")
-- access flag
local val = cfxZones.getFlagValue(param, theZone)
if not val or (val < 1) then val = 1 end
if val > #theZone.msgResponses then val = #theZone.msgResponses end
val = theZone.msgResponses[val]
val = dcsCommon.trim(val)
-- replace pattern in original with new val
outMsg = string.gsub(outMsg, pattern, val, 1) -- only one sub!
end
until not startLoc
-- rnd response
local rndRsp = dcsCommon.pickRandom(theZone.msgResponses)
outMsg = outMsg:gsub ("<rrnd>", rndRsp)
end
return outMsg
end
function messenger.processDynamicTime(inMsg, theZone)
-- replace all occurences of <v: flagName> with their values
local pattern = "<t:%s*[%s%w%*%d%.%-_]+>" -- no list allowed but blanks and * and . and - and _ --> we fail on the other specials to keep this simple
local outMsg = inMsg
repeat -- iterate all patterns one by one
local startLoc, endLoc = string.find(outMsg, pattern)
if startLoc then
local theValParam = string.sub(outMsg, startLoc, endLoc)
-- strip lead and trailer
local param = string.gsub(theValParam, "<t:%s*", "")
param = string.gsub(param, ">","")
-- access flag
local val = cfxZones.getFlagValue(param, theZone)
-- use this to process as time value
--trigger.action.outText("time: accessing <" .. param .. "> and received <" .. val .. ">", 30)
local timeString = dcsCommon.processHMS(theZone.msgTimeFormat, val)
if not timeString then timeString = "NULL" end
-- replace pattern in original with new val
outMsg = string.gsub(outMsg, pattern, timeString, 1) -- only one sub!
end
until not startLoc
return outMsg
end
function messenger.processDynamicLoc(inMsg, theZone)
-- replace all occurences of <lat/lon/ele/mgrs: flagName> with their values
-- agl = angels
-- vel = velocity (speed)
-- hdg = heading
-- rhdg = heading, response-mapped
local locales = {"lat", "lon", "ele", "mgrs", "lle", "latlon", "alt", "vel", "hdg", "rhdg", "type"}
local outMsg = inMsg
local uHead = 0
for idx, aLocale in pairs(locales) do
local pattern = "<" .. aLocale .. ":%s*[%s%w%*%d%.%-_]+>"
repeat -- iterate all patterns one by one
local startLoc, endLoc = string.find(outMsg, pattern)
if startLoc then
local theValParam = string.sub(outMsg, startLoc, endLoc)
-- strip lead and trailer
local param = string.gsub(theValParam, "<" .. aLocale .. ":%s*", "")
param = string.gsub(param, ">","")
-- find zone or unit
param = dcsCommon.trim(param)
local thePoint = nil
local tZone = cfxZones.getZoneByName(param)
local tUnit = Unit.getByName(param)
local spd = 0
local angels = 0
local theType = "<errType>"
if tZone then
theType = "Zone"
thePoint = cfxZones.getPoint(tZone)
if tZone.linkedUnit and Unit.isExist(tZone.linkedUnit) then
local lU = tZone.linkedUnit
local masterPoint = lU:getPoint()
thePoint.y = masterPoint.y
spd = dcsCommon.getUnitSpeed(lU)
spd = math.floor(spd * 3.6)
uHead = math.floor(dcsCommon.getUnitHeading(tUnit) * 57.2958) -- to degrees.
else
-- since zones always have elevation of 0,
-- now get the elevation from the map
thePoint.y = land.getHeight({x = thePoint.x, y = thePoint.z})
end
elseif tUnit then
if Unit.isExist(tUnit) then
theType = tUnit:getTypeName()
thePoint = tUnit:getPoint()
spd = dcsCommon.getUnitSpeed(tUnit)
-- convert m/s to km/h
spd = math.floor(spd * 3.6)
uHead = math.floor(dcsCommon.getUnitHeading(tUnit) * 57.2958) -- to degrees.
end
else
-- nothing to do, remove me.
end
local locString = theZone.errString
if thePoint then
-- now that we have a point, we can do locale-specific
-- processing. return result in locString
local lat, lon, alt = coord.LOtoLL(thePoint)
lat, lon = dcsCommon.latLon2Text(lat, lon)
angels = math.floor(thePoint.y)
if theZone.imperialUnits then
alt = math.floor(alt * 3.28084) -- feet
spd = math.floor(spd * 0.539957) -- km/h to knots
angels = math.floor(angels * 3.28084)
else
alt = math.floor(alt) -- meters
end
if angels > 1000 then
angels = math.floor(angels / 100) * 100
end
if aLocale == "lat" then locString = lat
elseif aLocale == "lon" then locString = lon
elseif aLocale == "ele" then locString = tostring(alt)
elseif aLocale == "lle" then locString = lat .. " " .. lon .. " ele " .. tostring(alt)
elseif aLocale == "latlon" then locString = lat .. " " .. lon
elseif aLocale == "alt" then locString = tostring(angels) -- don't confuse alt and angels, bad var naming here
elseif aLocale == "vel" then locString = tostring(spd)
elseif aLocale == "hdg" then locString = tostring(uHead)
elseif aLocale == "type" then locString = theType
elseif aLocale == "rhdg" and (theZone.msgResponses) then
local offset = messenger.rspMapper360(uHead, #theZone.msgResponses)
locString = dcsCommon.trim(theZone.msgResponses[offset])
else
-- we have mgrs
local grid = coord.LLtoMGRS(coord.LOtoLL(thePoint))
locString = grid.UTMZone .. ' ' .. grid.MGRSDigraph .. ' ' .. grid.Easting .. ' ' .. grid.Northing
end
end
-- replace pattern in original with new val
outMsg = string.gsub(outMsg, pattern, locString, 1) -- only one sub!
end -- if startloc
until not startLoc
end -- for all locales
return outMsg
end
function messenger.rspMapper360(directionInDegrees, numResponses)
-- maps responses like clock. Clock has 12 'responses' (12, 1, .., 11),
-- with the first (12) also mapping to the last half arc
-- this method dynamically 'winds' the responses around
-- a clock and returns the index of the message to display
if numResponses < 1 then numResponses = 1 end
directionInDegrees = math.floor(directionInDegrees)
while directionInDegrees < 0 do directionInDegrees = directionInDegrees + 360 end
while directionInDegrees >= 360 do directionInDegrees = directionInDegrees - 360 end
-- now we have 0..360
-- calculate arc per item
local arcPerItem = 360 / numResponses
local halfArc = arcPerItem / 2
-- we now map 0..360 to (0-halfArc..360-halfArc) by shifting
-- direction by half-arc and clipping back 0..360
-- and now we can directly derive the index of the response
directionInDegrees = directionInDegrees + halfArc
if directionInDegrees >= 360 then directionInDegrees = directionInDegrees - 360 end
local index = math.floor(directionInDegrees / arcPerItem) + 1 -- 1 .. numResponses
return index
end
-- Dynamic Group and Dynamic Unit processing are
-- unique to messenger, and are not available via
-- cfxZones or dcsCommon
--
function messenger.dynamicGroupProcessing(msg, theZone, theGroup)
if not theGroup then return msg end
@ -455,7 +202,7 @@ function messenger.dynamicUnitProcessing(inMsg, theZone, theUnit)
elseif aLocale == "hnd" then locString = dcsCommon.getGeneralDirection(direction)
elseif aLocale == "sde" then locString = dcsCommon.getNauticalDirection(direction)
elseif aLocale == "rbea" and (theZone.msgResponses) then
local offset = messenger.rspMapper360(direction, #theZone.msgResponses)
local offset = cfxZones.rspMapper360(direction, #theZone.msgResponses)
locString = dcsCommon.trim(theZone.msgResponses[offset])
else locString = "<locale " .. aLocale .. " err: undefined params>"
end
@ -469,27 +216,14 @@ function messenger.dynamicUnitProcessing(inMsg, theZone, theUnit)
end
function messenger.dynamicFlagProcessing(inMsg, theZone)
if not inMsg then return "No in message" end
if not theZone then return "Nil zone" end
-- process <v: xxx>
local msg = messenger.processDynamicValues(inMsg, theZone)
-- process <t: xxx>
msg = messenger.processDynamicTime(msg, theZone)
-- process lat / lon / ele / mgrs
msg = messenger.processDynamicLoc(msg, theZone)
return msg
end
--
-- reat attributes
--
function messenger.createMessengerWithZone(theZone)
-- start val - a range
local aMessage = cfxZones.getStringFromZoneProperty(theZone, "message", "")
theZone.message = messenger.preProcMessage(aMessage, theZone)
theZone.message = aMessage -- refactoring: messenger.preProcMessage(aMessage, theZone) removed
theZone.spaceBefore = cfxZones.getBoolFromZoneProperty(theZone, "spaceBefore", false)
theZone.spaceAfter = cfxZones.getBoolFromZoneProperty(theZone, "spaceAfter", false)
@ -584,6 +318,7 @@ function messenger.createMessengerWithZone(theZone)
-- flag whose value can be read: to be deprecated
if cfxZones.hasProperty(theZone, "messageValue?") then
theZone.messageValue = cfxZones.getStringFromZoneProperty(theZone, "messageValue?", "<none>")
trigger.action.outText("+++Msg: Warning - zone <" .. theZone.name .. "> uses 'messageValue' attribute. Migrate to <v:<flag> now!")
end
-- time format for new <t: flagname>
@ -606,7 +341,7 @@ function messenger.createMessengerWithZone(theZone)
end
if messenger.verbose or theZone.verbose then
trigger.action.outText("+++Msg: new zone <".. theZone.name .."> will say <".. theZone.message .. ">", 30)
trigger.action.outText("+++Msg: new messenger in <".. theZone.name .."> will say '".. theZone.message .. "' (raw)", 30)
end
end
@ -620,29 +355,23 @@ function messenger.getMessage(theZone)
if not zName then zName = "<strange!>" end
local zVal = "<n/a>"
if theZone.messageValue then
trigger.action.outText("+++Msg: Warning - zone <" .. theZone.name .. "> uses 'messageValue' attribute. Migrate to <v:<flag> now!")
zVal = cfxZones.getFlagValue(theZone.messageValue, theZone)
zVal = tostring(zVal)
if not zVal then zVal = "<err>" end
end
-- replace *zone and *value wildcards
--msg = string.gsub(msg, "*name", zName)-- deprecated
--msg = string.gsub(msg, "*value", zVal) -- deprecated
-- old-school <v> to provide value from messageValue
-- to be removed mid-2023
msg = string.gsub(msg, "<v>", zVal)
local z = tonumber(zVal)
if not z then z = 0 end
msg = dcsCommon.processHMS(msg, z)
-- process <t> [classic format], <latlon> and <mrgs>
msg = messenger.dynamicProcessClassic(msg, theZone)
-- remainder hand-off to cfxZones (refactoring of messenger code
msg = cfxZones.processStringWildcards(msg, theZone, theZone.msgTimeFormat, theZone.imperialUnits, theZone.msgResponses)
-- now add new processing of <x: flagname> access
msg = messenger.dynamicFlagProcessing(msg, theZone)
-- now add new processinf of <lat: flagname>
-- also handles <lon:x>, <ele:x>, <mgrs:x>
return msg
end
@ -789,7 +518,3 @@ if not messenger.start() then
messenger = nil
end
--[[--
--]]--

448
modules/valet.lua Normal file
View File

@ -0,0 +1,448 @@
valet = {}
valet.version = "1.0.0"
valet.verbose = false
valet.requiredLibs = {
"dcsCommon", -- always
"cfxZones", -- Zones, of course
}
valet.valets = {}
--[[--
Version History
1.0.0 - initial version
--]]--
function valet.addValet(theZone)
table.insert(valet.valets, theZone)
end
function valet.getValetByName(aName)
for idx, aZone in pairs(valet.valets) do
if aName == aZone.name then return aZone end
end
if valet.verbose then
trigger.action.outText("+++valet: no valet with name <" .. aName ..">", 30)
end
return nil
end
--
-- read attributes
--
function valet.createValetWithZone(theZone)
-- start val - a range
theZone.inSoundFile = cfxZones.getStringFromZoneProperty(theZone, "inSoundFile", "<none>")
if cfxZones.hasProperty(theZone, "firstInSoundFile") then
theZone.firstInSoundFile = cfxZones.getStringFromZoneProperty(theZone, "firstInSoundFile", "<none>")
end
theZone.outSoundFile = cfxZones.getStringFromZoneProperty(theZone, "outSoundFile", "<none>")
-- greeting/first greeting, handle if "" = no text out
if cfxZones.hasProperty(theZone, "firstGreeting") then
theZone.firstGreeting = cfxZones.getStringFromZoneProperty(theZone, "firstGreeting", "")
end
theZone.greeting = cfxZones.getStringFromZoneProperty(theZone, "greeting", "")
theZone.greetSpawns = cfxZones.getBoolFromZoneProperty(theZone, "greetSpawns", false)
-- goodbye
theZone.goodbye = cfxZones.getStringFromZoneProperty(theZone, "goodbye", "")
theZone.duration = cfxZones.getNumberFromZoneProperty(theZone, "duration", 30) -- warning: crossover from messenger. Intentional
-- valetMethod for outputs
theZone.valetMethod = cfxZones.getStringFromZoneProperty(theZone, "method", "inc")
if cfxZones.hasProperty(theZone, "valetMethod") then
theZone.valetMethod = cfxZones.getStringFromZoneProperty(theZone, "valetMethod", "inc")
end
-- outputs
if cfxZones.hasProperty(theZone, "hi!") then
theZone.valetHi = cfxZones.getStringFromZoneProperty(theZone, "hi!", "*<none>")
end
if cfxZones.hasProperty(theZone, "bye!") then
theZone.valetBye = cfxZones.getStringFromZoneProperty(theZone, "bye!", "*<none>")
end
-- reveiver: coalition, group, unit
if cfxZones.hasProperty(theZone, "coalition") then
theZone.valetCoalition = cfxZones.getCoalitionFromZoneProperty(theZone, "coalition", 0)
elseif cfxZones.hasProperty(theZone, "valetCoalition") then
theZone.valetCoalition = cfxZones.getCoalitionFromZoneProperty(theZone, "valetCoalition", 0)
end
if cfxZones.hasProperty(theZone, "types") then
local types = cfxZones.getStringFromZoneProperty(theZone, "types", "")
theZone.valetTypes = dcsCommon.string2Array(types, ",")
elseif cfxZones.hasProperty(theZone, "valetTypes") then
local types = cfxZones.getStringFromZoneProperty(theZone, "valetTypes", "")
theZone.valetTypes = dcsCommon.string2Array(groups, ",")
end
if cfxZones.hasProperty(theZone, "groups") then
local groups = cfxZones.getStringFromZoneProperty(theZone, "groups", "<none>")
theZone.valetGroups = dcsCommon.string2Array(groups, ",")
elseif cfxZones.hasProperty(theZone, "valetGroups") then
local groups = cfxZones.getStringFromZoneProperty(theZone, "valetGroups", "<none>")
theZone.valetGroups = dcsCommon.string2Array(groups, ",")
end
if cfxZones.hasProperty(theZone, "units") then
local units = cfxZones.getStringFromZoneProperty(theZone, "units", "<none>")
theZone.valetUnits = dcsCommon.string2Array(units, ",")
elseif cfxZones.hasProperty(theZone, "valetUnits") then
local units = cfxZones.getStringFromZoneProperty(theZone, "valetUnits", "<none>")
theZone.valetUnits = dcsCommon.string2Array(units, ",")
end
if (theZone.valetGroups and theZone.valetUnits) or
(theZone.valetGroups and theZone.valetCoalition) or
(theZone.valetUnits and theZone.valetCoalition)
then
trigger.action.outText("+++valet: WARNING - valet in <" .. theZone.name .. "> may have coalition, group or unit. Use only one.", 30)
end
theZone.imperialUnits = cfxZones.getBoolFromZoneProperty(theZone, "imperial", false)
if cfxZones.hasProperty(theZone, "imperialUnits") then
theZone.imperialUnits = cfxZones.getBoolFromZoneProperty(theZone, "imperialUnits", false)
end
theZone.valetTimeFormat = cfxZones.getStringFromZoneProperty(theZone, "timeFormat", "<:h>:<:m>:<:s>")
-- collect all players currently in-zone.
-- since we start the game, there is no player in-game, can skip
theZone.playersInZone = {}
end
--
-- Update
--
function valet.preprocessWildcards(inMsg, aUnit, theDesc)
local theMsg = inMsg
local pName = "Unknown"
if aUnit.getPlayerName then
pN = aUnit:getPlayerName()
if pN then pName = pN end
end
theMsg = theMsg:gsub("<player>", pName)
theMsg = theMsg:gsub("<unit>", aUnit:getName())
theMsg = theMsg:gsub("<type>", aUnit:getTypeName())
theMsg = theMsg:gsub("<group>", aUnit:getGroup():getName())
theMsg = theMsg:gsub("<in>", tostring(theDesc.greets + 1) )
theMsg = theMsg:gsub("<out>", tostring(theDesc.byes + 1))
return theMsg
end
function valet.greetPlayer(playerName, aPlayerUnit, theZone, theDesc)
--trigger.action.outText("valet.greetPlayer <" .. theZone.name .. "> enter", 30)
-- player has just entred zone
local msg = theZone.greeting
local dur = theZone.duration
local fileName = "l10n/DEFAULT/" .. theZone.inSoundFile
local ID = aPlayerUnit:getID()
-- see if this was the first time, and if so, if we have a special first message
if theDesc.greets < 1 then
if theZone.firstGreeting then
msg = theZone.firstGreeting
end
if theZone.firstInSoundFile then
fileName = "l10n/DEFAULT/" .. theZone.firstInSoundFile
end
end
if msg == "<none>" then msg = "" end
if not msg then msg = "" end
-- an empty string suppresses message/sound
if msg ~= "" then
if theZone.verbose then
trigger.action.outText("+++valet: <" .. theZone.name .. "> - 'greet' triggers for player <" .. playerName .. "> in <" .. aPlayerUnit:getName() .. ">", 30)
end
-- process and say meessage
msg = valet.preprocessWildcards(msg, aPlayerUnit, theDesc)
msg = cfxZones.processStringWildcards(msg, theZone, theZone.valetTimeFormat, theZone.imperialUnits) -- nil responses
-- now always output only to the player
trigger.action.outTextForUnit(ID, msg, dur)
end
-- always play, if no sound file found it will have no effect
trigger.action.outSoundForUnit(ID, fileName)
-- update desc
theDesc.currentlyIn = true
theDesc.greets = theDesc.greets + 1
-- bang output
if theZone.valetHi then
cfxZones.pollFlag(theZone.valetHi, theZone.valetMethod, theZone)
if theZone.verbose or valet.verbose then
trigger.action.outText("+++valet: banging output 'hi!' with <" .. theZone.valetMethod .. "> on <" .. theZone.valetHi .. "> for zone " .. theZone.name, 30)
end
end
end
function valet.sendOffPlayer(playerName, aPlayerUnit, theZone, theDesc)
-- player has left the area
local msg = theZone.goodbye or ""
local dur = theZone.duration
local fileName = "l10n/DEFAULT/" .. theZone.inSoundFile
local ID = aPlayerUnit:getID()
if msg == "<none>" then msg = "" end
-- an empty string suppresses message/sound
if msg ~= "" then
-- process and say meessage
msg = valet.preprocessWildcards(msg, aPlayerUnit, theDesc)
msg = cfxZones.processStringWildcards(msg, theZone, theZone.valetTimeFormat, theZone.imperialUnits) -- nil responses
trigger.action.outTextForUnit(ID, msg, dur)
end
-- always play sound
trigger.action.outSoundForUnit(ID, fileName)
-- update desc
theDesc.currentlyIn = false
theDesc.byes = theDesc.byes + 1
-- bang output
if theZone.valetBye then
cfxZones.pollFlag(theZone.valetBye, theZone.valetMethod, theZone)
if theZone.verbose or valet.verbose then
trigger.action.outText("+++valet: banging output 'bye!' with <" .. theZone.valetMethod .. "> on <" .. theZone.valetBye .. "> for zone " .. theZone.name, 30)
end
end
end
function valet.checkZoneAgainstPlayers(theZone, allPlayers)
-- check status of all players if they are inside or
-- outside the zone (done during update)
-- when a change happens, react to it
local p = cfxZones.getPoint(theZone)
p.y = 0 -- sanity first
local maxRad = theZone.maxRadius
-- set up hysteresis
local outside = maxRad * 1.2
for playerName, aPlayerUnit in pairs (allPlayers) do
local unitName = aPlayerUnit:getName()
local uP = aPlayerUnit:getPoint()
local uCoa = aPlayerUnit:getCoalition()
local uGroup = aPlayerUnit:getGroup()
local groupName = uGroup:getName()
--local cat = aPlayerUnit:getDesc().category -- note indirection!
local uType = aPlayerUnit:getTypeName()
if theZone.valetCoalition and theZone.valetCoalition ~= uCoa then
-- coalition mismatch -- no checks required
elseif theZone.valetGroups and not dcsCommon.wildArrayContainsString(theZone.valetGroups, groupName) then
-- group name mismatch, skip checks
elseif theZone.valetUnits and not dcsCommon.wildArrayContainsString(theZone.valetUnits, unitName) then
-- unit name mismatch, skip checks
elseif theZone.valetTypes and not dcsCommon.wildArrayContainsString(theZone.valetTypes, uType) then
-- types dont match
else
local theDesc = theZone.playersInZone[playerName] -- may be nil
uP.y = 0 -- mask out y
local dist = dcsCommon.dist(p, uP) -- get distance
if cfxZones.pointInZone(uP, theZone) then
-- the unit is inside the zone.
-- see if it was inside last time
-- if new player, create new record, start as outside
if not theDesc then
theDesc = {}
theDesc.currentlyIn = false
theDesc.greets = 0
theDesc.byes = 0
theDesc.unitName = unitName
theZone.playersInZone[playerName] = theDesc
else
if theDesc.unitName == unitName then
else
-- ha!!! player changed planes!
theDesc.currentlyIn = false
theDesc.greets = 0
theDesc.byes = 0
theDesc.unitName = unitName
end
end
if not theDesc.currentlyIn then
-- we detect a change. Need to greet
valet.greetPlayer(playerName, aPlayerUnit, theZone, theDesc)
end
elseif (dist > outside) and theDesc then
if theDesc.unitName == unitName then
else
-- ha!!! player changed planes!
theDesc.currentlyIn = false
theDesc.greets = 0
theDesc.byes = 0
theDesc.unitName = unitName
end
if theDesc.currentlyIn then
-- unit is definitely outside and was inside before
-- (there's a record in this zone's playersInZone
valet.sendOffPlayer(playerName, aPlayerUnit, theZone, theDesc)
else
-- was outside before
end
else
-- we are in the twilight zone (hysteresis). Do nothing.
end
end -- else do checks
end
end
function valet.update()
-- call me in a second to poll triggers
timer.scheduleFunction(valet.update, {}, timer.getTime() + 1)
-- collect all players
local allPlayers = {}
-- single-player first
local sp = world.getPlayer() -- returns unit
if sp then
local playerName = sp:getPlayerName()
if playerName then
allPlayers[playerName] = sp
end
end
-- now clients
local coalitions = {0, 1, 2}
for isx, aCoa in pairs (coalitions) do
local coaClients = coalition.getPlayers(aCoa)
for idy, aUnit in pairs (coaClients) do
if aUnit.getPlayerName and aUnit:getPlayerName() then
allPlayers[aUnit:getPlayerName()] = aUnit
end
end
end
for idx, theZone in pairs(valet.valets) do
valet.checkZoneAgainstPlayers(theZone, allPlayers)
end
end
--
-- OnEvent - detecting player enter unit
--
function valet.checkPlayerSpawn(playerName, theUnit)
-- see if player spawned in a valet zone
if not playerName then return end
if not theUnit then return end
local pos = theUnit:getPoint()
--trigger.action.outText("+++valet: spawn event", 30)
for idx, theZone in pairs(valet.valets) do
-- erase any old records
theZone.playersInZone[playerName] = nil
-- create new if in that valet zone
if cfxZones.pointInZone(pos, theZone) then
theDesc = {}
theDesc.currentlyIn = true -- suppress messages
if theZone.greetSpawns then
theDesc.currentlyIn = false
end
theDesc.greets = 0
theDesc.byes = 0
theDesc.unitName = theUnit:getName()
theZone.playersInZone[playerName] = theDesc
if theZone.verbose then
trigger.action.outText("+++valet: spawning player <" .. playerName .. "> / <" .. theUnit:getName() .. "> in valet <" .. theZone.name .. ">", 40)
end
end
end
end
function valet:onEvent(event)
if event.id == 20 then
if not event.initiator then return end
local theUnit = event.initiator
if not theUnit.getPlayerName then
trigger.action.outText("+++valet: non player event 20(?)", 30)
return
end
local pName = theUnit:getPlayerName()
if not pName then
trigger.action.outText("+++valet: nil player name on event 20 (!)", 30)
return
end
valet.checkPlayerSpawn(pName, theUnit)
end
end
--
-- Config & Start
--
function valet.readConfigZone()
local theZone = cfxZones.getZoneByName("valetConfig")
if not theZone then
if valet.verbose then
trigger.action.outText("+++msgr: NO config zone!", 30)
end
theZone = cfxZones.createSimpleZone("valetConfig")
end
valet.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false)
if valet.verbose then
trigger.action.outText("+++msgr: read config", 30)
end
end
function valet.start()
-- lib check
if not dcsCommon.libCheck then
trigger.action.outText("cfx valet requires dcsCommon", 30)
return false
end
if not dcsCommon.libCheck("cfx valet", valet.requiredLibs) then
return false
end
-- read config
valet.readConfigZone()
-- process valet Zones
-- old style
local attrZones = cfxZones.getZonesWithAttributeNamed("valet")
for k, aZone in pairs(attrZones) do
valet.createValetWithZone(aZone) -- process attributes
valet.addValet(aZone) -- add to list
end
-- register event handler
world.addEventHandler(valet)
-- start update
timer.scheduleFunction(valet.update, {}, timer.getTime() + 1)
trigger.action.outText("cfx valet v" .. valet.version .. " started.", 30)
return true
end
-- let's go!
if not valet.start() then
trigger.action.outText("cfx valet aborted: missing libraries", 30)
valet = nil
end