mirror of
https://github.com/weyne85/DML.git
synced 2025-10-29 16:57:49 +00:00
Version 1.2.2
New Valet Module Wildcard refactoring Cloner to delicates integration
This commit is contained in:
parent
43d598f035
commit
654b782894
Binary file not shown.
Binary file not shown.
@ -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]
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
--
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
448
modules/valet.lua
Normal 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
|
||||
BIN
tutorial & demo missions/demo - I say hello goodbye.miz
Normal file
BIN
tutorial & demo missions/demo - I say hello goodbye.miz
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user