Version 2.0.0

Version II goes live!
This commit is contained in:
Christian Franz 2024-01-11 10:20:59 +01:00
parent 663038db0a
commit c9694c3176
59 changed files with 1407 additions and 2272 deletions

Binary file not shown.

Binary file not shown.

View File

@ -1,25 +1,22 @@
cfxSSBSingleUse = {}
cfxSSBSingleUse.version = "1.1.0"
--[[--
Version History
1.0.0 - Initial version
1.1.0 - importing dcsCommon, cfxGroup for simplicity
- save unit name on player enter unit as look-up
- determining ground-start
- place wreck in slot
1.1.1 - guarding against nil playerName
- using 15 (birth) instead of 20 (player enter)
WHAT IT IS
SSB Single Use is a script that blocks a player slot
after that plane crashes.
--]]--
cfxSSBSingleUse.enabledFlagValue = 0 -- DO NOT CHANGE, MUST MATCH SSB
cfxSSBSingleUse.disabledFlagValue = cfxSSBSingleUse.enabledFlagValue + 100 -- DO NOT CHANGE
cfxSSBSingleUse.version = "2.0.0"
cfxSSBSingleUse.verbose = true
cfxSSBSingleUse.useDebris = false
cfxSSBSingleUse.requiredLibs = {
"dcsCommon", -- no cfxZones. a rarity!
"cfxMX",
}
--[[--
Version History
2.0.0 - cfxMX homologization
- startup checks
- minimal verbosity
- useDebris switch, currently set off
- clean-up
--]]--
cfxSSBSingleUse.playerUnits = {}
cfxSSBSingleUse.slotGroundActions = {
@ -29,7 +26,6 @@ cfxSSBSingleUse.slotGroundActions = {
"From Ground Area",
"From Ground Area Hot",
}
cfxSSBSingleUse.groundSlots = {} -- players that start on the ground
function cfxSSBSingleUse:onEvent(event)
@ -40,7 +36,6 @@ function cfxSSBSingleUse:onEvent(event)
-- if we get here, initiator is set
local theUnit = event.initiator -- we know this exists
-- write down player names and planes
if event.id == 15 then
local uName = theUnit:getName()
@ -69,7 +64,9 @@ function cfxSSBSingleUse:onEvent(event)
if not thePilot then
-- ignore. not a player plane
trigger.action.outText("+++singleUse: ignored crash for NPC unit <" .. uName .. ">", 30)
if cfxSSBSingleUse.verbose then
trigger.action.outText("+++singleUse: ignored crash for NPC unit <" .. uName .. ">", 30)
end
return
end
@ -80,19 +77,24 @@ function cfxSSBSingleUse:onEvent(event)
local theGroundSlot = cfxSSBSingleUse.groundSlots[gName]
if theGroundSlot then
local unitType = theUnit:getTypeName()
trigger.action.outText("+++singleUse: <" .. uName .. "> starts on Ground. Will place debris for " .. unitType .. " NOW!!!", 30)
if cfxSSBSingleUse.verbose then
trigger.action.outText("+++singleUse: <" .. uName .. "> starts on Ground. Will place debris for " .. unitType .. " NOW!!!", 30)
end
cfxSSBSingleUse.placeDebris(unitType, theGroundSlot)
end
-- block this slot.
trigger.action.setUserFlag(gName, cfxSSBSingleUse.disabledFlagValue)
trigger.action.outText("+++singleUse: blocked <" .. gName .. "> after " .. thePilot .. " crashed it.", 30)
if cfxSSBSingleUse.verbose then
trigger.action.outText("+++singleUse: blocked <" .. gName .. "> after " .. thePilot .. " crashed it.", 30)
end
end
end
function cfxSSBSingleUse.placeDebris(unitType, theGroundSlot)
if not unitType then return end
if not cfxSSBSingleUse.useDebris then return end
-- access location one, we assume single-unit groups
-- or at least that the player sits in unit one
local playerData = theGroundSlot.playerUnits
@ -106,12 +108,14 @@ function cfxSSBSingleUse.placeDebris(unitType, theGroundSlot)
wreckData.type = unitType
coalition.addStaticObject(theGroundSlot.coaNum, wreckData )
trigger.action.outText("+++singleUse: wreck <" .. unitType .. "> at " .. wreckData.x .. ", " .. wreckData.y .. " for " .. wreckData.name, 30)
if cfxSSBSingleUse.verbose then
trigger.action.outText("+++singleUse: wreck <" .. unitType .. "> at " .. wreckData.x .. ", " .. wreckData.y .. " for " .. wreckData.name, 30)
end
end
function cfxSSBSingleUse.populateAirfieldSlots()
local pGroups = cfxGroups.getPlayerGroup()
local pGroups = cfxMX.getPlayerGroup()
local groundStarters = {}
for idx, theGroup in pairs(pGroups) do
-- we always use the first player's plane as referenced
@ -122,33 +126,35 @@ function cfxSSBSingleUse.populateAirfieldSlots()
if dcsCommon.arrayContainsString(cfxSSBSingleUse.slotGroundActions, action ) then
-- ground starter, not from runway
groundStarters[theGroup.name] = theGroup
trigger.action.outText("+++singleUse: <" .. theGroup.name .. "> is ground starter", 30)
if cfxSSBSingleUse.verbose then
trigger.action.outText("+++singleUse: <" .. theGroup.name .. "> is ground starter", 30)
end
end
end
cfxSSBSingleUse.groundSlots = groundStarters
end
function cfxSSBSingleUse.start()
-- check libs
if not dcsCommon.libCheck("cfx SSB Single Use",
cfxSSBSingleUse.requiredLibs) then
return false
end
-- install event monitor
world.addEventHandler(cfxSSBSingleUse)
-- get all groups and process them to find
-- all planes that are on the ground for
-- eye candy
cfxSSBSingleUse.populateAirfieldSlots()
-- turn on ssb
trigger.action.setUserFlag("SSB",100)
trigger.action.outText("SSB Single use v" .. cfxSSBSingleUse.version .. " running", 30)
return true
end
-- let's go!
cfxSSBSingleUse.start()
--[[--
Additional features (later):
- place a wreck in slot when blocking for eye candy
- record player when they enter a unit and only block player planes
--]]--
if not cfxSSBSingleUse.start()then
trigger.action.outText("SSB Single Use failed to start up.", 30)
cfxSSBSingleUse = nil
end

View File

@ -1,5 +1,5 @@
tdz = {}
tdz.version = "1.0.1"
tdz.version = "1.0.2"
tdz.requiredLibs = {
"dcsCommon", -- always
"cfxZones", -- Zones, of course
@ -15,6 +15,8 @@ VERSION HISTORY
multiple zone support
hops detection improvement
helo attribute
1.0.2 - manual placement option
filters FARPs
--]]--
@ -80,22 +82,39 @@ end
--
function tdz.createTDZ(theZone)
local p = theZone:getPoint()
local theBase = dcsCommon.getClosestAirbaseTo(p) -- never get FARPS
local theBase = dcsCommon.getClosestAirbaseTo(p, 0) -- never get FARPS
theZone.base = theBase
theZone.baseName = theBase:getName()
theZone.helos = false
-- get closest runway to TDZ
-- may get a bit hairy, so let's find a good way
local allRwys = theBase:getRunways()
local nearestRwy = nil
local minDist = math.huge
for idx, aRwy in pairs(allRwys) do
local rp = aRwy.position
local dist = dcsCommon.distFlat(p, rp)
if dist < minDist then
nearestRwy = aRwy
minDist = dist
-- see if this is a manually placed runway
if theZone:getBoolFromZoneProperty("manual", true) then
-- construct runway from trigger zone attributes
if theZone.verbose or tdz.verbose then
trigger.action.outText("+++TDZ: runway for <" .. theZone.name .. "> is manually placed", 30)
end
nearestRwy = {}
nearestRwy.length = theZone:getNumberFromZoneProperty("length", 2500)
nearestRwy.width = theZone:getNumberFromZoneProperty("width", 60)
local hdgRaw = theZone:getNumberFromZoneProperty("course", 0) -- in degrees
hdgRaw = hdgRaw * 0.0174533 -- rads
nearestRwy.course = hdgRaw * (-1) -- why? because DCS.
nearestRwy.position = theZone:getPoint(true)
theZone.baseName = theZone.name .. "(manual placement)"
else
-- get closest runway to TDZ
-- may get a bit hairy, so let's find a good way
local allRwys = theBase:getRunways()
local minDist = math.huge
for idx, aRwy in pairs(allRwys) do
local rp = aRwy.position
local dist = dcsCommon.distFlat(p, rp)
if dist < minDist then
nearestRwy = aRwy
minDist = dist
end
end
end
local bearing = nearestRwy.course * (-1)

View File

@ -1,13 +1,13 @@
airfield = {}
airfield.version = "1.1.2"
airfield.version = "2.0.0"
airfield.requiredLibs = {
"dcsCommon",
"cfxZones",
}
airfield.verbose = false
airfield.myAirfields = {} -- indexed by name
airfield.farps = false
airfield.gracePeriod = 3
airfield.allAirfields = {} -- inexed by name
--[[--
This module generates signals when the nearest airfield changes hands,
can force the coalition of an airfield, and always provides the
@ -24,17 +24,47 @@ airfield.gracePeriod = 3
1.1.2 - 'show' attribute
line color attributes per zone
line color defaults in config
2.0.0 - show all airfields option
- fully reworked show options
- unmanaged airfields are automatically updated
- full color support
-- support for FARPS as well
--]]--
-- init all airfields DB
function airfield.collectAll()
local allBases = world.getAirbases() -- get all
local count = 0
local dropped = 0
for idx, aBase in pairs(allBases) do
local entry = {}
local cat = Airbase.getCategory(aBase) -- DCS 2.9 hardened
-- cats: 0 = airfield, 1 = farp, 2 = ship
if (cat == 0) or (cat == 1) then
local name = aBase:getName()
entry.base = aBase
entry.cat = cat
-- entry.linkedTo holds zone if linked to. that how we know
airfield.allAirfields[name] = entry
count = count + 1
else
dropped = dropped + 1
end
end
if airfield.verbose then
trigger.action.outText("+++airF: init - count = <" .. count .. ">, dropped = <" .. dropped .. ">", 30)
end
end
--
-- setting up airfield
--
function airfield.createAirFieldFromZone(theZone)
--trigger.action.outText("Enter airfield for <" .. theZone.name .. ">", 30)
theZone.farps = theZone:getBoolFromZoneProperty("farps", false)
local filterCat = 0
if (airfield.farps or theZone.farps) then filterCat = {0, 1} end -- bases and farps
if (theZone.farps) then filterCat = {0, 1} end -- bases and farps
local p = theZone:getPoint()
local theBase = dcsCommon.getClosestAirbaseTo(p, filterCat)
theZone.airfield = theBase
@ -114,10 +144,30 @@ function airfield.createAirFieldFromZone(theZone)
airfield.showAirfield(theZone)
--trigger.action.outText("Exit airfield for <" .. theZone.name .. ">", 30)
-- now mark this zone as handled
local entry = airfield.allAirfields[theZone.afName]
entry.linkedTo = theZone -- only remember last, but that's enough.
end
function airfield.markAirfieldOnMap(theAirfield, lineColor, fillColor)
local markID = dcsCommon.numberUUID()
local radius = 2000 -- meters
local p = theAirfield:getPoint()
-- if there are runways, we center on first runway
local rws = theAirfield:getRunways()
if rws then -- all airfields and farps have runways defined, but that array isnt filled for FARPS
local rw1 = rws[1]
if rw1 then
p.x = rw1.position.x
p.z = rw1.position.z
end
end
p.y = 0
trigger.action.circleToAll(-1, markID, p, radius, lineColor, fillColor, 1, true, "")
return markID
end
function airfield.showAirfield(theZone)
if not theZone then return end
if theZone.ownerMark then
@ -138,34 +188,8 @@ function airfield.showAirfield(theZone)
fillColor = theZone.neutralFill -- {0.8, 0.8, 0.8, 0.2}
end
-- always center on airfield, always 2km radius
local markID = dcsCommon.numberUUID()
local radius = 2000 -- meters
local p = theZone.airfield:getPoint()
-- if there are runways, we center on first runway
local rws = theZone.airfield:getRunways()
if rws then -- all airfields and farps have runways, but that array isnt filled for FARPS
local rw1 = rws[1]
if rw1 then
p.x = rw1.position.x
p.z = rw1.position.z
if airfield.verbose or theZone.verbose then
trigger.action.outText("+++airF: zone <" .. theZone.name .. "> assoc airfield <" .. theZone.afName .. "> has rw1, x=" .. p.x .. ", z=" .. p.z, 30)
end
else
if airfield.verbose or theZone.verbose then
trigger.action.outText("+++airF: zone <" .. theZone.name .. "> assoc airfield <" .. theZone.afName .. "> has no rw1", 30)
end
end
else
if airfield.verbose or theZone.verbose then
trigger.action.outText("+++airF: zone <" .. theZone.name .. "> assoc airfield <" .. theZone.afName .. "> has no runways", 30)
end
end
p.y = 0
theZone.ownerMark = airfield.markAirfieldOnMap(theZone.airfield, lineColor, fillColor)
trigger.action.circleToAll(-1, markID, p, radius, lineColor, fillColor, 1, true, "")
theZone.ownerMark = markID
end
@ -188,12 +212,40 @@ end
--
-- event handling
--
function airfield.untendedCapture(theName, theBase)
if airfield.showAll and airfield.allAirfields[theName] then
-- we draw and handle all airfields, even those
-- without an attached handler zone
local theEntry = airfield.allAirfields[theName]
if not theEntry.linkedTo then -- merely safety
if theEntry.ownerMark then
-- remove previous mark
trigger.action.removeMark(theEntry.ownerMark)
theEntry.ownerMark = nil
end
local owner = theBase:getCoalition()
local lineColor = airfield.redLine
local fillColor = airfield.redFill
if owner == 2 then
lineColor = airfield.blueLine
fillColor = airfield.blueFill
elseif owner == 0 or owner == 3 then
lineColor = airfield.neutralLine
fillColor = airfield.neutralFill
end
theEntry.ownerMark = airfield.markAirfieldOnMap(theBase, lineColor, fillColor)
end
end
end
function airfield.airfieldCaptured(theBase)
-- retrieve the zone that controls this airfield
local bName = theBase:getName()
local theZone = airfield.myAirfields[bName]
if not theZone then return end -- not handled
if not theZone then
airfield.untendedCapture(bName, theBase)
return
end -- not attached to a zone
local newCoa = theBase:getCoalition()
theZone.owner = newCoa
@ -227,15 +279,8 @@ function airfield:onEvent(event)
-- get category
local desc = theBase:getDesc()
local bName = theBase:getName()
local cat = desc.category -- never get cat directly!
--[[-- if cat == 1 then
if not airfield.farps then
if airfield.verbose then
trigger.action.outText("+++airF: ignored cap event for FARP <" .. bName .. ">", 30)
end
return
end
end --]]
local cat = desc.category -- never get cat directly! DCS 2.0 safe
if airfield.verbose then
trigger.action.outText("+++airF: cap event for <" .. bName .. ">, cat = (" .. cat .. ")", 30)
end
@ -391,7 +436,7 @@ function airfield.readConfig()
theZone = cfxZones.createSimpleZone("airfieldConfig")
end
airfield.verbose = theZone.verbose
airfield.farps = theZone:getBoolFromZoneProperty("farps", false)
-- airfield.farps = theZone:getBoolFromZoneProperty("farps", false)
-- colors for line and fill
airfield.redLine = theZone:getRGBAVectorFromZoneProperty("redLine", {1.0, 0, 0, 1.0})
@ -400,12 +445,25 @@ function airfield.readConfig()
airfield.blueFill = theZone:getRGBAVectorFromZoneProperty("blueFill", {0.0, 0, 1.0, 0.2})
airfield.neutralLine = theZone:getRGBAVectorFromZoneProperty("neutralLine", {0.8, 0.8, 0.8, 1.0})
airfield.neutralFill = theZone:getRGBAVectorFromZoneProperty("neutralFill", {0.8, 0.8, 0.8, 0.2})
airfield.showAll = theZone:getBoolFromZoneProperty("show", false)
end
function airfield.showUnlinked()
for name, entry in pairs(airfield.allAirfields) do
if not entry.linkedTo then
airfield.untendedCapture(name, entry.base)
end
end
end
function airfield.start()
if not dcsCommon.libCheck("cfx airfield", airfield.requiredLibs)
then return false end
-- set up DB
airfield.collectAll()
-- read config
airfield.readConfig()
@ -415,6 +473,9 @@ function airfield.start()
airfield.createAirFieldFromZone(aZone)
end
-- show all unlinked
if airfield.showAll then airfield.showUnlinked() end
-- connect event handler
world.addEventHandler(airfield)
@ -442,7 +503,3 @@ if not airfield.start() then
trigger.action.outText("+++ aborted airfield v" .. airfield.version .. " -- startup failed", 30)
airfield = nil
end
--[[--
ideas: what if we made this airfield movable?
--]]--

View File

@ -1,5 +1,5 @@
cfxArtilleryUI = {}
cfxArtilleryUI.version = "1.1.0"
cfxArtilleryUI.version = "2.0.0"
cfxArtilleryUI.requiredLibs = {
"dcsCommon", -- always
"cfxPlayer", -- get all players
@ -8,7 +8,7 @@ cfxArtilleryUI.requiredLibs = {
}
--
-- UI for ArtilleryZones module, implements LOS, Observer, SMOKE
-- Copyright (c) 2021, 2022 by Christian Franz and cf/x AG
-- Copyright (c) 2021 - 2024 by Christian Franz and cf/x AG
--[[-- VERSION HISTORY
- 1.0.0 - based on jtacGrpUI
@ -21,6 +21,8 @@ cfxArtilleryUI.requiredLibs = {
- collect zones recognizes moving zones, updates landHeight
- allSeeing god mode attribute: always observing.
- allRanging god mode attribute: always in range.
- 2.0.0 - dmlZones, OOP
cleanup
--]]--
@ -272,21 +274,18 @@ function cfxArtilleryUI.populateTargetMenu(conf)
-- now compare old control string with new, and only
-- re-populate if the old is different
if tgtCheckSum == conf.tgtCheckSum then
-- trigger.action.outText("*** yeah old targets", 30)
return
elseif not conf.tgtCheckSum then
-- trigger.action.outText("+++ new target menu", 30)
else
trigger.action.outTextForGroup(conf.id, "Artillery target updates", 30)
trigger.action.outSoundForGroup(conf.id, cfxArtilleryUI.updateSound)
-- trigger.action.outText("!!! target update ", 30)
end
-- we need to re-populate. erase old values
cfxArtilleryUI.clearCommsTargets(conf)
conf.tgtCheckSum = tgtCheckSum -- remember for last time
--trigger.action.outText("new targets", 30)
if #filteredTargets < 1 then
-- simply put one-line dummy in there
local commandTxt = "(No unobscured target areas)"
@ -307,7 +306,6 @@ function cfxArtilleryUI.populateTargetMenu(conf)
for i=1, numTargets do
-- make a target command for each
local aTarget = filteredTargets[i]
commandTxt = "Fire at: <" .. aTarget.name .. ">"
theCommand = missionCommands.addCommandForGroup(
conf.id,
@ -444,7 +442,6 @@ function cfxArtilleryUI.doCommandListTargets(args)
local conf = args[1] -- < conf in here
local what = args[2] -- < second argument in here
local theGroup = conf.theGroup
-- trigger.action.outTextForGroup(conf.id, "+++ groupUI: processing comms menu for <" .. what .. ">", 30)
local targetList = cfxArtilleryUI.collectArtyTargets(conf)
-- iterate the list
if #targetList < 1 then
@ -453,7 +450,6 @@ function cfxArtilleryUI.doCommandListTargets(args)
end
local desc = "Artillery Targets:\n"
-- trigger.action.outTextForGroup(conf.id, "Target Report:", 30)
for i=1, #targetList do
local aTarget = targetList[i]
local inRange = cfxArtilleryUI.allRanging or aTarget.range * 1000 < aTarget.spotRange
@ -475,7 +471,6 @@ end
function cfxArtilleryUI.collectArtyTargets(conf)
-- iterate all target zones, for those that are on my side
-- calculate range, bearing, and then order by distance
local theTargets = {}
for idx, aZone in pairs(cfxArtilleryZones.artilleryZones) do
if aZone.coalition == conf.coalition then
@ -498,7 +493,7 @@ function cfxArtilleryUI.collectArtyTargets(conf)
--aTarget.targetName = aZone.name
aTarget.spotRange = aZone.spotRange
-- get the target we are lazing
local zP = cfxZones.getPoint(aZone) -- zone can move!
local zP = aZone:getPoint() -- zone can move!
aZone.landHeight = land.getHeight({x = zP.x, y= zP.z})
local there = {x = zP.x, y = aZone.landHeight + 1, z=zP.z}
aTarget.there = there
@ -654,18 +649,17 @@ function cfxArtilleryUI.readConfigZone()
-- note: must match exactly!!!!
local theZone = cfxZones.getZoneByName("ArtilleryUIConfig")
if not theZone then
trigger.action.outText("+++A-UI: no config zone!", 30)
return
--trigger.action.outText("+++A-UI: no config zone!", 30)
--return
theZone = cfxZones.createSimpleZone("ArtilleryUIConfig")
end
trigger.action.outText("+++A-UI: found config zone!", 30)
cfxArtilleryUI.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false)
cfxArtilleryUI.allowPlanes = cfxZones.getBoolFromZoneProperty(theZone, "allowPlanes", false)
cfxArtilleryUI.smokeColor = cfxZones.getSmokeColorStringFromZoneProperty(theZone, "smokeColor", "red")
cfxArtilleryUI.allSeeing = cfxZones.getBoolFromZoneProperty(theZone, "allSeeing", false)
cfxArtilleryUI.allRanging = cfxZones.getBoolFromZoneProperty(theZone, "allRanging", false)
cfxArtilleryUI.allTiming = cfxZones.getBoolFromZoneProperty(theZone, "allTiming", false)
cfxArtilleryUI.verbose = theZone.verbose
cfxArtilleryUI.allowPlanes = theZone:getBoolFromZoneProperty("allowPlanes", false)
cfxArtilleryUI.smokeColor = theZone:getSmokeColorStringFromZoneProperty("smokeColor", "red")
cfxArtilleryUI.allSeeing = theZone:getBoolFromZoneProperty("allSeeing", false)
cfxArtilleryUI.allRanging = theZone:getBoolFromZoneProperty("allRanging", false)
cfxArtilleryUI.allTiming = theZone:getBoolFromZoneProperty("allTiming", false)
end
--
@ -681,7 +675,6 @@ function cfxArtilleryUI.start()
cfxArtilleryUI.readConfigZone()
-- iterate existing groups so we have a start situation
-- now iterate through all player groups and install the Assault Troop Menu
local allPlayerGroups = cfxPlayerGroups -- cfxPlayerGroups is a global, don't fuck with it!
-- contains per group player record. Does not resolve on unit level!
for gname, pgroup in pairs(allPlayerGroups) do
@ -710,5 +703,5 @@ end
--[[--
TODO: transition times based on distance - requires real bound arty first
DONE: ui for smoking target zone: list ten closest zones, and provide menu to smoke zone
TODO: remove dependency on cfxPlayer
--]]--

View File

@ -1,5 +1,5 @@
cfxArtilleryZones = {}
cfxArtilleryZones.version = "2.2.2"
cfxArtilleryZones.version = "3.0.0"
cfxArtilleryZones.requiredLibs = {
"dcsCommon", -- always
"cfxZones", -- Zones, of course
@ -31,28 +31,12 @@ cfxArtilleryZones.verbose = false
2.2.0 - DML Watchflag integration
2.2.1 - minor code clean-up
2.2.2 - new doParametricFireAt()
3.0.0 - dmlZones, OOP
- cleanup
Artillery Target Zones *** EXTENDS ZONES ***
Target Zones for artillery. Can determine which zones are in range and visible and then handle artillery barrage to this zone
Copyright (c) 2021, 2022 by Christian Franz and cf/x AG
USAGE
Via ME: Add the relevant attributes to the zone
Via Script: Use createArtilleryTarget()
Callbacks
when fire at target is invoked, a callback can be
invoked so your code knows that fire control has been
given a command or that projectiles are impacting.
Signature
callback(rason, zone, data), with
reason: 'firing' - fire command given for zone
'impact' a projectile has hit
zone: artilleryZone
data: empty on 'fire'
.point where impact point
.strength power of explosion
Copyright (c) 2021 - 2024 by Christian Franz and cf/x AG
--]]--
cfxArtilleryZones.artilleryZones = {}
@ -117,42 +101,39 @@ function cfxArtilleryZones.createArtilleryTarget(name, point, coalition, spotRan
end
function cfxArtilleryZones.processArtilleryZone(aZone)
aZone.artilleryTarget = cfxZones.getStringFromZoneProperty(aZone, "artilleryTarget", aZone.name)
aZone.coalition = cfxZones.getCoalitionFromZoneProperty(aZone, "coalition", 0) -- side that marks it on map, and who fires arty
aZone.spotRange = cfxZones.getNumberFromZoneProperty(aZone, "spotRange", 3000) -- FO max range to direct fire
aZone.shellStrength = cfxZones.getNumberFromZoneProperty(aZone, "shellStrength", 500) -- power of shells (strength)
aZone.artilleryTarget = aZone:getStringFromZoneProperty( "artilleryTarget", aZone.name)
aZone.coalition = aZone:getCoalitionFromZoneProperty("coalition", 0) -- side that marks it on map, and who fires arty
aZone.spotRange = aZone:getNumberFromZoneProperty("spotRange", 3000) -- FO max range to direct fire
aZone.shellStrength = aZone:getNumberFromZoneProperty("shellStrength", 500) -- power of shells (strength)
aZone.shellNum = cfxZones.getNumberFromZoneProperty(aZone, "shellNum", 17) -- number of shells in bombardment
aZone.transitionTime = cfxZones.getNumberFromZoneProperty(aZone, "transitionTime", 20) -- average time of travel for projectiles
aZone.addMark = cfxZones.getBoolFromZoneProperty(aZone, "addMark", true) -- note: defaults to true
aZone.shellVariance = cfxZones.getNumberFromZoneProperty(aZone, "shellVariance", 0.2) -- strength of explosion can vary by +/- this amount
aZone.shellNum = aZone:getNumberFromZoneProperty("shellNum", 17) -- number of shells in bombardment
aZone.transitionTime = aZone:getNumberFromZoneProperty( "transitionTime", 20) -- average time of travel for projectiles
aZone.addMark = aZone:getBoolFromZoneProperty("addMark", true) -- note: defaults to true
aZone.shellVariance = aZone:getNumberFromZoneProperty( "shellVariance", 0.2) -- strength of explosion can vary by +/- this amount
-- watchflag:
-- triggerMethod
aZone.artyTriggerMethod = cfxZones.getStringFromZoneProperty(aZone, "artyTriggerMethod", "change")
aZone.artyTriggerMethod = aZone:getStringFromZoneProperty( "artyTriggerMethod", "change")
if cfxZones.hasProperty(aZone, "triggerMethod") then
aZone.artyTriggerMethod = cfxZones.getStringFromZoneProperty(aZone, "triggerMethod", "change")
if aZone:hasProperty("triggerMethod") then
aZone.artyTriggerMethod = aZone:getStringFromZoneProperty("triggerMethod", "change")
end
if cfxZones.hasProperty(aZone, "f?") then
aZone.artyTriggerFlag = cfxZones.getStringFromZoneProperty(aZone, "f?", "none")
end
if cfxZones.hasProperty(aZone, "artillery?") then
aZone.artyTriggerFlag = cfxZones.getStringFromZoneProperty(aZone, "artillery?", "none")
end
if cfxZones.hasProperty(aZone, "in?") then
aZone.artyTriggerFlag = cfxZones.getStringFromZoneProperty(aZone, "in?", "none")
if aZone:hasProperty("f?") then
aZone.artyTriggerFlag = cfxZones.getStringFromZoneProperty("f?", "none")
elseif aZone:hasProperty("artillery?") then
aZone.artyTriggerFlag = aZone:getStringFromZoneProperty("artillery?", "none")
elseif aZone:hasProperty("in?") then
aZone.artyTriggerFlag = aZone:getStringFromZoneProperty("in?", "none")
end
if aZone.artyTriggerFlag then
aZone.lastTriggerValue = trigger.misc.getUserFlag(aZone.artyTriggerFlag) -- save last value
end
aZone.cooldown =cfxZones.getNumberFromZoneProperty(aZone, "cooldown", 120) -- seconds
aZone.baseAccuracy = cfxZones.getNumberFromZoneProperty(aZone, "baseAccuracy", aZone.radius) -- meters from center radius shell impact
aZone.cooldown = aZone:getNumberFromZoneProperty("cooldown", 120) -- seconds
aZone.baseAccuracy = aZone:getNumberFromZoneProperty( "baseAccuracy", aZone.radius) -- meters from center radius shell impact
-- use zone radius as mase accuracy for simple placement
aZone.silent = cfxZones.getBoolFromZoneProperty(aZone, "silent", false)
aZone.silent = aZone:getBoolFromZoneProperty("silent", false)
end
function cfxArtilleryZones.addArtilleryZone(aZone)
@ -200,7 +181,7 @@ function cfxArtilleryZones.artilleryZonesInRangeOfUnit(theUnit)
-- is it one of mine?
if aZone.coalition == myCoalition then
-- is it close enough?
local zP = cfxZones.getPoint(aZone)
local zP = aZone:getPoint()
aZone.landHeight = land.getHeight({x = zP.x, y= zP.z})
local zonePoint = {x = zP.x, y = aZone.landHeight, z = zP.z}
local d = dcsCommon.dist(p,zonePoint)
@ -249,7 +230,7 @@ end
--
--
-- BOOM command
-- BOOM method
--
function cfxArtilleryZones.doBoom(args)
trigger.action.explosion(args.point, args.strength)
@ -356,7 +337,6 @@ function cfxArtilleryZones.simFireAtZone(aZone, aGroup, dist)
trigger.action.outTextForCoalition(aGroup:getCoalition(), "Artillery firing on ".. aZone.name .. addInfo, 30)
end
--trigger.action.smoke(center, 2) -- mark location visually
end
function cfxArtilleryZones.simSmokeZone(aZone, aGroup, aColor)
@ -367,7 +347,7 @@ function cfxArtilleryZones.simSmokeZone(aZone, aGroup, aColor)
if type(aColor) == "string" then
aColor = dcsCommon.smokeColor2Num(aColor)
end
local zP = cfxZones.getPoint(aZone)
local zP = aZone:getPoint(aZone)
aZone.landHeight = land.getHeight({x = zP.x, y= zP.z})
local transitionTime = aZone.transitionTime --17 -- seconds until phosphor lands
@ -404,7 +384,7 @@ function cfxArtilleryZones.update()
-- iterate all zones to see if a trigger has changed
for idx, aZone in pairs(cfxArtilleryZones.artilleryZones) do
if cfxZones.testZoneFlag(aZone, aZone.artyTriggerFlag, aZone.artyTriggerMethod, "lastTriggerValue") then
if aZone:testZoneFlag(aZone.artyTriggerFlag, aZone.artyTriggerMethod, "lastTriggerValue") then
-- a triggered release!
cfxArtilleryZones.doFireAt(aZone) -- all from zone vars!
if cfxArtilleryZones.verbose then
@ -413,10 +393,10 @@ function cfxArtilleryZones.update()
end
end
-- old code
--[[--
if aZone.artyTriggerFlag then
local currTriggerVal = cfxZones.getFlagValue(aZone.artyTriggerFlag, aZone) -- trigger.misc.getUserFlag(aZone.artyTriggerFlag)
local currTriggerVal = cfxZones.getFlagValue(aZone.artyTriggerFlag, aZone)
if currTriggerVal ~= aZone.lastTriggerValue
then
-- a triggered release!
@ -430,6 +410,7 @@ function cfxArtilleryZones.update()
end
end
--]]--
end
end
@ -446,8 +427,6 @@ function cfxArtilleryZones.start()
-- collect all spawn zones
local attrZones = cfxZones.getZonesWithAttributeNamed("artilleryTarget")
-- now create a spawner for all, add them to the spawner updater, and spawn for all zones that are not
-- paused
for k, aZone in pairs(attrZones) do
cfxArtilleryZones.processArtilleryZone(aZone) -- process attribute and add to zone
cfxArtilleryZones.addArtilleryZone(aZone) -- remember it so we can smoke it

View File

@ -1,25 +1,17 @@
cfxCargoReceiver = {}
cfxCargoReceiver.version = "1.2.2"
cfxCargoReceiver.version = "2.0.0"
cfxCargoReceiver.ups = 1 -- once a second
cfxCargoReceiver.maxDirectionRange = 500 -- in m. distance when cargo manager starts talking to pilots who are carrying that cargo
cfxCargoReceiver.requiredLibs = {
"dcsCommon", -- always
"cfxPlayer", -- for directions
"cfxZones", -- Zones, of course
"cfxCargoManager", -- will notify me on a cargo event
}
--[[--
Version history
- 1.0.0 initial vbersion
- 1.1.0 added flag manipulation options
no negative agl on announcement
silent attribute
- 1.2.0 method
f!, cargoReceived!
- 1.2.1 cargoMethod
- 1.2.2 removed deprecated functions
corrected pollFlag bug (not passing zone along)
distance to receiver is given as distance to zone boundary
- 2.0.0 no more cfxPlayer Dependency
dmlZones, OOP
clean-up
CargoReceiver is a zone enhancement you use to be automatically
@ -28,13 +20,6 @@ cfxCargoReceiver.requiredLibs = {
*** EXTENDS ZONES
Callback signature:
cb(event, obj, name, zone) with
- event being string, currently defined: 'deliver'
- obj being the cargo object
- name being cargo object name
- zone in which cargo was dropped (if dropped)
--]]--
cfxCargoReceiver.receiverZones = {}
function cfxCargoReceiver.processReceiverZone(aZone) -- process attribute and add to zone
@ -42,50 +27,22 @@ function cfxCargoReceiver.processReceiverZone(aZone) -- process attribute and ad
-- isCargoReceiver flag and we are good
aZone.isCargoReceiver = true
-- we can add additional processing here
aZone.autoRemove = cfxZones.getBoolFromZoneProperty(aZone, "autoRemove", false) -- maybe add a removeDelay
aZone.removeDelay = cfxZones.getNumberFromZoneProperty(aZone, "removeDelay", 1)
aZone.autoRemove = aZone:getBoolFromZoneProperty("autoRemove", false) -- maybe add a removeDelay
aZone.removeDelay = aZone:getNumberFromZoneProperty("removeDelay", 1)
if aZone.removeDelay < 1 then aZone.removeDelay = 1 end
aZone.silent = cfxZones.getBoolFromZoneProperty(aZone, "silent", false)
aZone.silent = aZone:getBoolFromZoneProperty("silent", false)
--trigger.action.outText("+++rcv: recognized receiver zone: " .. aZone.name , 30)
-- same integration as object destruct detector for flags
--[[--
if cfxZones.hasProperty(aZone, "setFlag") then
aZone.setFlag = cfxZones.getStringFromZoneProperty(aZone, "setFlag", "999")
end
if cfxZones.hasProperty(aZone, "f=1") then
aZone.setFlag = cfxZones.getStringFromZoneProperty(aZone, "f=1", "999")
end
if cfxZones.hasProperty(aZone, "clearFlag") then
aZone.clearFlag = cfxZones.getStringFromZoneProperty(aZone, "clearFlag", "999")
end
if cfxZones.hasProperty(aZone, "f=0") then
aZone.clearFlag = cfxZones.getStringFromZoneProperty(aZone, "f=0", "999")
end
if cfxZones.hasProperty(aZone, "increaseFlag") then
aZone.increaseFlag = cfxZones.getStringFromZoneProperty(aZone, "increaseFlag", "999")
end
if cfxZones.hasProperty(aZone, "f+1") then
aZone.increaseFlag = cfxZones.getStringFromZoneProperty(aZone, "f+1", "999")
end
if cfxZones.hasProperty(aZone, "decreaseFlag") then
aZone.decreaseFlag = cfxZones.getStringFromZoneProperty(aZone, "decreaseFlag", "999")
end
if cfxZones.hasProperty(aZone, "f-1") then
aZone.decreaseFlag = cfxZones.getStringFromZoneProperty(aZone, "f-1", "999")
end
--]]--
-- new method support
aZone.cargoMethod = cfxZones.getStringFromZoneProperty(aZone, "method", "inc")
if cfxZones.hasProperty(aZone, "cargoMethod") then
aZone.cargoMethod = cfxZones.getStringFromZoneProperty(aZone, "cargoMethod", "inc")
aZone.cargoMethod = aZone:getStringFromZoneProperty("method", "inc")
if aZone:hasProperty("cargoMethod") then
aZone.cargoMethod = aZone:getStringFromZoneProperty("cargoMethod", "inc")
end
if cfxZones.hasProperty(aZone, "f!") then
aZone.outReceiveFlag = cfxZones.getStringFromZoneProperty(aZone, "f!", "*<none>")
elseif cfxZones.hasProperty(aZone, "cargoReceived!") then
aZone.outReceiveFlag = cfxZones.getStringFromZoneProperty(aZone, "cargoReceived!", "*<none>")
if aZone:hasProperty("f!") then
aZone.outReceiveFlag = aZone:getStringFromZoneProperty("f!", "*<none>")
elseif aZone:hasProperty("cargoReceived!") then
aZone.outReceiveFlag = aZone:getStringFromZoneProperty( "cargoReceived!", "*<none>")
end
end
@ -134,17 +91,14 @@ end
function cfxCargoReceiver.cargoEvent(event, object, name)
-- usually called from cargomanager
--trigger.action.outText("Cargo Receiver: event <" .. event .. "> for " .. name, 30)
if not event then return end
if event == "grounded" then
--trigger.action.outText("+++rcv: grounded for " .. name, 30)
-- this is actually the only one that interests us
if not object then
--trigger.action.outText("+++rcv: " .. name .. " has null object", 30)
return
end
if not object:isExist() then
--trigger.action.outText("+++rcv: " .. name .. " no longer exists", 30)
if not Object.isExist(object) then
return
end
loc = object:getPoint()
@ -156,28 +110,10 @@ function cfxCargoReceiver.cargoEvent(event, object, name)
cfxCargoReceiver.invokeCallback("deliver", object, name, aZone)
-- set flags as indicated
--[[--
if aZone.setFlag then
trigger.action.setUserFlag(aZone.setFlag, 1)
end
if aZone.clearFlag then
trigger.action.setUserFlag(aZone.clearFlag, 0)
end
if aZone.increaseFlag then
local val = trigger.misc.getUserFlag(aZone.increaseFlag) + 1
trigger.action.setUserFlag(aZone.increaseFlag, val)
end
if aZone.decreaseFlag then
local val = trigger.misc.getUserFlag(aZone.decreaseFlag) - 1
trigger.action.setUserFlag(aZone.decreaseFlag, val)
end
--]]--
if aZone.outReceiveFlag then
cfxZones.pollFlag(aZone.outReceiveFlag, aZone.cargoMethod, aZone)
end
--trigger.action.outText("+++rcv: " .. name .. " delivered in zone " .. aZone.name, 30)
--trigger.action.outSound("Quest Snare 3.wav")
if aZone.autoRemove then
-- schedule this for in a few seconds?
local args = {}
@ -218,12 +154,12 @@ function cfxCargoReceiver.update()
-- this cargo can be talked down.
-- find the player unit that is closest to in in hopes
-- that that is the one carrying it
local allPlayers = cfxPlayer.getAllPlayers() -- idx by name
for pname, info in pairs(allPlayers) do
local allPlayers = dcsCommon.getAllExistingPlayersAndUnits() -- idx by name
for pname, theUnit in pairs(allPlayers) do
-- iterate all player units
local closestUnit = nil
local minDelta = math.huge
local theUnit = info.unit
--local theUnit = info.unit
if theUnit:isExist() then
local uPoint = theUnit:getPoint()
local currDelta = dcsCommon.distFlat(thePoint, uPoint)
@ -305,4 +241,4 @@ if not cfxCargoReceiver.start() then
end
-- TODO: config zone for talking down pilots
-- TODO: f+/f-/f=1/f=0
-- detect all pilots in zone (not clear: are all detected or only one)

View File

@ -1,411 +0,0 @@
cfxArtilleryDemon = {}
cfxArtilleryDemon.version = "1.0.3"
-- based on cfx stage demon v 1.0.2
--[[--
Version History
1.0.2 - taken from stageDemon
1.0.3 - corrected 'messageOut' bug
--]]--
cfxArtilleryDemon.messageToAll = true -- set to false if messages should be sent only to the group that set the mark
cfxArtilleryDemon.messageTime = 30 -- how long a message stays on the sceeen
-- cfxArtillery hooks into DCS's mark system to intercept user
-- transactions with the mark system and uses that for arty targeting
-- used to interactively add ArtilleryZones during gameplay
-- Copyright (c) 2021 by Christian Franz and cf/x AG
cfxArtilleryDemon.autostart = true -- start automatically
-- whenever you begin a Mark with the string below, it will be taken as a command
-- and run through the command parser, stripping the mark, and then splitting
-- by blanks
cfxArtilleryDemon.markOfDemon = "-" -- all commands must start with this sequence
cfxArtilleryDemon.splitDelimiter = " "
cfxArtilleryDemon.unitFilterMethod = nil -- optional user filtering redirection. currently
-- set to allow all users use cfxArtillery
cfxArtilleryDemon.processCommandMethod = nil -- optional initial command processing redirection
-- currently set to cfxArtillery's own processor
cfxArtilleryDemon.commandTable = {} -- key, value pair for command processing per keyword
-- all commands cfxArtillery understands are used as keys and
-- the functions that process them are used as values
-- making the parser a trivial table :)
cfxArtilleryDemon.demonID = nil -- used only for suspending the event callback
-- unit authorization. You return false to disallow this unit access
-- to commands
-- simple authorization checks would be to allow only players
-- on neutral side, or players in range of location with Lino of sight
-- to that point
--
function cfxArtilleryDemon.authorizeAllUnits(event)
-- units/groups that are allowed to give a command can be filtered.
-- return true if the unit/group may give commands
-- cfxArtillery allows anyone to give it commands
return true
end
function cfxArtilleryDemon.hasMark(theString)
-- check if the string begins with the sequece to identify commands
if not theString then return false end
return theString:find(cfxArtilleryDemon.markOfDemon) == 1
end
function cfxArtilleryDemon.splitString(inputstr, sep)
if sep == nil then
sep = "%s"
end
if inputstr == nil then
inputstr = ""
end
local t={}
for str in string.gmatch(inputstr, "([^"..sep.."]+)") do
table.insert(t, str)
end
return t
end
function cfxArtilleryDemon.str2num(inVal, default)
if not default then default = 0 end
if not inVal then return default end
if type(inVal) == "number" then return inVal end
local num = nil
if type(inVal) == "string" then num = tonumber(inVal) end
if not num then return default end
return num
end
--
-- output method. can be customized, so we have a central place where we
-- can control how output is handled. Is currently outText and outTextToGroup
--
function cfxArtilleryDemon.outMessage(theMessage, args)
if not args then
args = {}
end
local toAll = args.toAll -- will only be true if defined and set to true
if not args.group then
toAll = true
else
if not args.group:isExist() then
toAll = true
end
end
toAll = toAll or cfxArtilleryDemon.messageToAll
if not toAll then
trigger.action.outTextToGroup(args.group, theMessage, cfxArtilleryDemon.messageTime)
else
trigger.action.outText(theMessage, cfxArtilleryDemon.messageTime)
end
end
--
-- get all player groups - since there is no getGroupByIndex in DCS (yet)
-- we simply collect all player groups (since only palyers can place marks)
-- and try to match their group ID to the one given by mark
function cfxArtilleryDemon.getAllPayerGroups()
local coalitionSides = {0, 1, 2} -- we currently have neutral, red, blue
local playerGroups = {}
for i=1, #coalitionSides do
local theSide = coalitionSides[i]
-- get all players for this side
local thePlayers = coalition.getPlayers(theSide)
for p=1, #thePlayers do
aPlayerUnit = thePlayers[p] -- docs say this is a unit table, not a person!
if aPlayerUnit:isExist() then
local theGroup = aPlayerUnit:getGroup()
if theGroup:isExist() then
local gID = theGroup:getID()
playerGroups[gID] = theGroup -- multiple players per group results in one group
end
end
end
end
return playerGroups
end
function cfxArtilleryDemon.retrieveGroupFromEvent(theEvent)
-- DEBUG CODE
if theEvent.initiator then
trigger.action.outText("EVENT: initiator set to " .. theEvent.initiator:getName(), 30)
else
trigger.action.outText("EVENT: NO INITIATOR", 30)
end
trigger.action.outText("EVENT: groupID = " .. theEvent.groupID, 30)
-- trivial case: initiator is set, and we can access the group
if theEvent.initiator then
if theEvent.initiator:isExist() then
return theEvent.initiator:getGroup()
end
end
-- ok, bad news: initiator wasn't filled. let's try the fallback: event.groupID
if theEvent.groupID and theEvent.groupID > 0 then
local playerGroups = cfxArtilleryDemon.getAllPayerGroups()
if playerGroups[theEvent.groupID] then
return palyerGroups[theEvent.groupID]
end
end
-- nope, return nil
return nil
end
-- main hook into DCS. Called whenever a Mark-related event happens
-- very simple: look if text begins with special sequence, and if so,
-- call the command processor. Note that you can hook your own command
-- processor in by changing the value of processCommandMethod
function cfxArtilleryDemon:onEvent(theEvent)
-- while we can hook into any of the three events,
-- we curently only utilize CHANGE Mark
if not (theEvent.id == world.event.S_EVENT_MARK_ADDED) and
not (theEvent.id == world.event.S_EVENT_MARK_CHANGE) and
not (theEvent.id == world.event.S_EVENT_MARK_REMOVED) then
-- not of interest for us, bye bye
return
end
-- build the messageOut() arg table
local args = {}
args.toAll = cfxArtilleryDemon.toAll
--
--
args.toAll = false -- FORCE GROUPS FOR DEBUGGING OF NEW CODE
--
--
if not args.toAll then
-- we want group-targeted messaging
-- so we need to retrieve the group
local theGroup = cfxArtilleryDemon.retrieveGroupFromEvent(theEvent)
if not theGroup then
args.toAll = true
trigger.action.outText("*** WARNING: cfxArtilleryDemon can't find group for command", 30)
else
args.group = theGroup
end
end
cfxArtilleryDemon.args = args -- copy reference so we can easily use it in messageOut
-- when we get here, we have a mark event
-- see if the unit filter lets it pass
if not cfxArtilleryDemon.unitFilterMethod(theEvent) then
return -- unit is not allowed to give demon orders. bye bye
end
if theEvent.id == world.event.S_EVENT_MARK_ADDED then
-- add mark is quite useless for us as we are called when the user clicks, with no
-- text in the description yet. Later abilities may want to use it though
end
if theEvent.id == world.event.S_EVENT_MARK_CHANGE then
-- when changed, the mark's text is examined for a command
-- if it starts with the 'mark' string ("*" by default) it is processed
-- by the command processor
-- if it is processed succesfully, the mark is immediately removed
-- else an error is displayed and the mark remains.
if cfxArtilleryDemon.hasMark(theEvent.text) then
-- strip the mark
local commandString = theEvent.text:sub(1+cfxArtilleryDemon.markOfDemon:len())
-- break remainder apart into <command> <arg1> ... <argn>
local commands = cfxArtilleryDemon.splitString(commandString, cfxArtilleryDemon.splitDelimiter)
-- this is a command. process it and then remove it if it was executed successfully
local success = cfxArtilleryDemon.processCommandMethod(commands, theEvent)
-- remove this mark after successful execution
if success then
trigger.action.removeMark(theEvent.idx)
cfxArtilleryDemon.outMessage("executed command <" .. commandString .. "> from unit" .. theEvent.initiator:getName(), args)
else
-- we could play some error sound
end
end
end
if theEvent.id == world.event.S_EVENT_MARK_REMOVED then
end
end
--
-- add / remove commands to/from cfxArtillerys vocabulary
--
function cfxArtilleryDemon.addCommndProcessor(command, processor)
cfxArtilleryDemon.commandTable[command:upper()] = processor
end
function cfxArtilleryDemon.removeCommandProcessor(command)
cfxArtilleryDemon.commandTable[command:upper()] = nil
end
--
-- process input arguments. Here we simply move them
-- up by one.
--
function cfxArtilleryDemon.getArgs(theCommands)
local args = {}
for i=2, #theCommands do
table.insert(args, theCommands[i])
end
return args
end
--
-- stage demon's main command interpreter.
-- magic lies in using the keywords as keys into a
-- function table that holds all processing functions
-- I wish we had that back in the Oberon days.
--
function cfxArtilleryDemon.executeCommand(theCommands, event)
-- trigger.action.outText("executor: *" .. theCommands[1] .. "*", 30)
-- see if theCommands[1] exists in the command table
local cmd = theCommands[1]
local arguments = cfxArtilleryDemon.getArgs(theCommands)
if not cmd then return false end
-- use the command as index into the table of functions
-- that handle them.
if cfxArtilleryDemon.commandTable[cmd:upper()] then
local theInvoker = cfxArtilleryDemon.commandTable[cmd:upper()]
local success = theInvoker(arguments, event)
return success
else
trigger.action.outText("***error: unknown command <".. cmd .. ">", 30)
return false
end
return true
end
--
-- SMOKE COMMAND
--
-- known commands and their processors
function cfxArtilleryDemon.smokeColor2Index (theColor)
local color = theColor:lower()
if color == "red" then return 1 end
if color == "white" then return 2 end
if color == "orange" then return 3 end
if color == "blue" then return 4 end
return 0
end
-- this is the command processing template for your own commands
-- when you add a command processor via addCommndProcessor()
-- smoke command syntax: '-smoke <color>' with optional color, color being red, green, blue, white or orange
function cfxArtilleryDemon.processSmokeCommand(args, event)
if not args[1] then args[1] = "red" end -- default to red color
local thePoint = event.pos
thePoint.y = land.getHeight({x = thePoint.x, y = thePoint.z}) +3 -- elevate to ground height
trigger.action.smoke(thePoint, cfxArtilleryDemon.smokeColor2Index(args[1]))
return true
end
--
-- BOOM command
--
function cfxArtilleryDemon.doBoom(args)
--trigger.action.outText("sim shell str=" .. args.strength .. " x=" .. args.point.x .. " z = " .. args.point.z .. " Tdelta = " .. args.tDelta, 30)
-- trigger.action.smoke(args.point, 2)
trigger.action.explosion(args.point, args.strength)
end
function cfxArtilleryDemon.processBoomCommand(args, event)
if not args[1] then args[1] = "750" end -- default to 750 strength
local transitionTime = 20 -- seconds until shells hit
local shellNum = 17
local shellBaseStrength = 500
local shellvariance = 0.2 -- 10%
local center = event.pos -- center of where shells hit
center.y = land.getHeight({x = center.x, y = center.z}) + 3
-- we now can 'dirty' the position by something. not yet
for i=1, shellNum do
local thePoint = dcsCommon.randomPointInCircle(100, 0, center.x, center.z)
local boomArgs = {}
local strVar = shellBaseStrength * shellvariance
strVar = strVar * (2 * dcsCommon.randomPercent() - 1.0) -- go from -1 to 1
boomArgs.strength = shellBaseStrength + strVar
thePoint.y = land.getHeight({x = thePoint.x, y = thePoint.z}) + 1 -- elevate to ground height + 1
boomArgs.point = thePoint
local timeVar = 5 * (2 * dcsCommon.randomPercent() - 1.0) -- +/- 1.5 seconds
boomArgs.tDelta = timeVar
timer.scheduleFunction(cfxArtilleryDemon.doBoom, boomArgs, timer.getTime() + transitionTime + timeVar)
end
trigger.action.outText("Fire command confirmed. Artillery is firing at your designated co-ordinates.", 30)
trigger.action.smoke(center, 2) -- mark location visually
return true
end
--
-- cfxArtilleryZones interface
--
function cfxArtilleryDemon.processTargetCommand(args, event)
-- get position
local center = event.pos -- center of where shells hit
center.y = land.getHeight({x = center.x, y = center.z})
if not event.initiator then
trigger.action.outText("Target entry aborted: no initiator.", 30)
return true
end
local theUnit = event.initiator
local theGroup = theUnit:getGroup()
local coalition = theGroup:getCoalition()
local spotRange = 3000
local autoAdd = true
local params = ""
for idx, param in pairs(args) do
if params == "" then params = ": "
else params = params .. " "
end
params = params .. param
end
local name = "TgtData".. params .. " (" .. theUnit:getName() .. ")@T+" .. math.floor(timer.getTime())
-- feed into arty zones
cfxArtilleryZones.createArtilleryZone(name, center, coalition, spotRange, 500, autoAdd) -- 500 is base strength
trigger.action.outTextForCoalition(coalition, "New ARTY coordinates received from " .. theUnit:getName() .. ", standing by", 30)
return true
end
--
-- cfxArtillery init and start
--
function cfxArtilleryDemon.init()
cfxArtilleryDemon.unitFilterMethod = cfxArtilleryDemon.authorizeAllUnits
cfxArtilleryDemon.processCommandMethod = cfxArtilleryDemon.executeCommand
-- now add known commands to interpreter. Add your own commands the same way
cfxArtilleryDemon.addCommndProcessor("smoke", cfxArtilleryDemon.processSmokeCommand)
cfxArtilleryDemon.addCommndProcessor("bumm", cfxArtilleryDemon.processBoomCommand)
cfxArtilleryDemon.addCommndProcessor("tgt",
cfxArtilleryDemon.processTargetCommand)
-- you can add and remove command the same way
trigger.action.outText("cf/x cfx Artillery Demon v" .. cfxArtilleryDemon.version .. " loaded", 30)
end
function cfxArtilleryDemon.start()
cfxArtilleryDemon.demonID = world.addEventHandler(cfxArtilleryDemon)
trigger.action.outText("cf/x cfxArtilleryDemon v" .. cfxArtilleryDemon.version .. " started", 30)
end
cfxArtilleryDemon.init()
if cfxArtilleryDemon.autostart then
cfxArtilleryDemon.start()
end

View File

@ -1,5 +1,5 @@
cfxMX = {}
cfxMX.version = "1.2.6"
cfxMX.version = "2.0.0"
cfxMX.verbose = false
--[[--
Mission data decoder. Access to ME-built mission structures
@ -7,26 +7,11 @@ cfxMX.verbose = false
Copyright (c) 2022, 2023 by Christian Franz and cf/x AG
Version History
1.0.0 - initial version
1.0.1 - getStaticFromDCSbyName()
1.1.0 - getStaticFromDCSbyName also copies groupID when not fetching orig
- on start up collects a cross reference table of all
original group id
- add linkUnit for statics
1.2.0 - added group name reference table
- added group type reference
- added references for allFixed, allHelo, allGround, allSea, allStatic
1.2.1 - added countryByName
- added linkByName
1.2.2 - fixed ctry bug in countryByName
- playerGroupByName
- playerUnitByName
1.2.3 - groupTypeByName
- groupCoalitionByName
1.2.4 - playerUnit2Group cross index
1.2.5 - unitIDbyName index added
1.2.6 - cfxMX.allTrainsByName
- train carve-outs for vehicles
2.0.0 - clean-up
- harmonized with cfxGroups
--]]--
cfxMX.groupNamesByID = {}
cfxMX.groupIDbyName = {}
@ -47,13 +32,21 @@ cfxMX.playerGroupByName = {} -- returns data only if a player is in group
cfxMX.playerUnitByName = {} -- returns data only if this is a player unit
cfxMX.playerUnit2Group = {} -- returns a group data for player units.
cfxMX.groups = {} -- all groups indexed b yname, cfxGroups folded into cfxMX
--[[-- group objects are
{
name= "",
coalition = "" (red, blue, neutral),
coanum = # (0, 1, 2 for neutral, red, blue)
category = "" (helicopter, ship, plane, vehicle, static),
hasPlayer = true/false,
playerUnits = {} (for each player unit in group: name, point, action)
}
--]]--
function cfxMX.getGroupFromDCSbyName(aName, fetchOriginal)
if not fetchOriginal then fetchOriginal = false end
-- fetch the group description for goup named aName (if exists)
-- returned structure must be parsed for useful information
-- returns data, category, countyID and coalitionID
-- unless fetchOriginal is true, creates a deep clone of
-- group data structure
for coa_name_miz, coa_data in pairs(env.mission.coalition) do -- iterate all coalitions
local coa_name = coa_name_miz
@ -135,11 +128,6 @@ function cfxMX.getStaticFromDCSbyName(aName, fetchOriginal)
if type(cntry_data) == 'table' then -- filter strings .id and .name
for obj_type_name, obj_type_data in pairs(cntry_data) do
if obj_type_name == "static"
-- obj_type_name == "helicopter" or
-- obj_type_name == "ship" or
-- obj_type_name == "plane" or
-- obj_type_name == "vehicle" or
-- obj_type_name == "static"
then -- (only look at statics)
local category = obj_type_name
if ((type(obj_type_data) == 'table') and obj_type_data.group and (type(obj_type_data.group) == 'table') and (#obj_type_data.group > 0)) then --there's at least one static in group!
@ -149,7 +137,7 @@ function cfxMX.getStaticFromDCSbyName(aName, fetchOriginal)
if group_data and group_data.route and group_data.route and group_data.route.points[1] then
linkUnit = group_data.route.points[1].linkUnit
if linkUnit then
--trigger.action.outText("MX: found missing link to " .. linkUnit .. " in " .. group_data.name, 30)
end
end
@ -207,7 +195,7 @@ function cfxMX.createCrossReferences()
obj_type_name == "plane" or
obj_type_name == "vehicle" or
obj_type_name == "static" -- what about "cargo"?
-- not that trains appear as 'vehicle'
-- note that trains appear as 'vehicle'
then -- (so it's not id or name)
local category = obj_type_name
if ((type(obj_type_data) == 'table') and obj_type_data.group and (type(obj_type_data.group) == 'table') and (#obj_type_data.group > 0)) then --there's at least one group!
@ -252,6 +240,12 @@ function cfxMX.createCrossReferences()
end
-- now iterate all units in this group
-- for unit xref like player info and ID
local hasPlayer = false
local playerUnits = {}
local groupName = group_data.name
if env.mission.version > 7 then -- translate raw to actual
groupName = env.getValueDictByKey(groupName)
end
for unit_num, unit_data in pairs(group_data.units) do
if unit_data.skill then
if unit_data.skill == "Client" or unit_data.skill == "Player" then
@ -259,10 +253,38 @@ function cfxMX.createCrossReferences()
cfxMX.playerUnitByName[unit_data.name] = unit_data
cfxMX.playerGroupByName[aName] = group_data -- inefficient, but works
cfxMX.playerUnit2Group[unit_data.name] = group_data
hasPlayer = true
local playerData = {}
playerData.name = unit_data.name
playerData.point = {}
playerData.point.x = unit_data.x
playerData.point.y = 0
playerData.point.z = unit_data.y
playerData.action = "none" -- default
-- access initial waypoint data by 'reaching up'
-- into group data and extract route.points[1]
if group_data.route and group_data.route.points and (#group_data.route.points > 0) then
playerData.action = group_data.route.points[1].action
end
table.insert(playerUnits, playerData)
end -- if unit skill client
end -- if has skill
cfxMX.unitIDbyName[unit_data.name] = unit_data.unitId
end -- for all units
local entry = {}
entry.name = groupName
entry.coalition = coa_name
entry.coaNum = coaNum
entry.category = category
entry.hasPlayer = hasPlayer
entry.playerUnits = playerUnits
-- add to db
cfxMX.groups[groupName] = entry
end -- for all groups
end --if has category data
end --if plane, helo etc... category
@ -274,6 +296,31 @@ function cfxMX.createCrossReferences()
end --for all coalitions in mission
end
-- return all groups that can have players in them
-- includes groups that currently are not or not anymore alive
function cfxMX.getPlayerGroup()
local playerGroups = {}
for gName, gData in pairs (cfxMX.groups) do
if gData.hasPlayer then
table.insert(playerGroups, gData)
end
end
return playerGroups
end
-- return all group names that can have players in them
-- includes groups that currently are not or not anymore alive
function cfxMX.getPlayerGroupNames()
local playerGroups = {}
for gName, gData in pairs (cfxMX.groups) do
if gData.hasPlayer then
table.insert(playerGroups, gName)
end
end
return playerGroups
end
function cfxMX.catText2ID(inText)
local outCat = 0 -- airplane
local c = inText:lower()
@ -283,7 +330,7 @@ function cfxMX.catText2ID(inText)
if c == "vehicle" then outCat = 2 end
if c == "train" then outCat = 4 end
if c == "static" then outCat = -1 end
--trigger.action.outText("cat2text: in <" .. inText .. "> out <" .. outCat .. ">", 30)
return outCat
end
@ -292,6 +339,7 @@ function cfxMX.start()
if cfxMX.verbose then
trigger.action.outText("cfxMX: "..#cfxMX.groupNamesByID .. " groups processed successfully", 30)
end
trigger.action.outText("cfxMX v." .. cfxMX.version .. " started.", 30)
end
-- start

View File

@ -1,5 +1,5 @@
cfxZones = {}
cfxZones.version = "4.0.10"
cfxZones.version = "4.1.1"
-- cf/x zone management module
-- reads dcs zones and makes them accessible and mutable
@ -9,31 +9,6 @@ cfxZones.version = "4.0.10"
--
--[[-- VERSION HISTORY
- 3.0.0 - support for DCS 2.8 linkUnit attribute, integration with
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
- 3.0.3 - new getLinkedUnit()
- 3.0.4 - new createRandomPointOnZoneBoundary()
- 3.0.5 - getPositiveRangeFromZoneProperty() now also supports upper bound (optional)
- 3.0.6 - new createSimplePolyZone()
- new createSimpleQuadZone()
- 3.0.7 - getPoint() can also get land y when passing true as second param
- 3.0.8 - new cfxZones.pointInOneOfZones(thePoint, zoneArray, useOrig)
- 3.0.9 - new getFlareColorStringFromZoneProperty()
- 3.1.0 - new getRGBVectorFromZoneProperty()
new getRGBAVectorFromZoneProperty()
- 3.1.1 - getRGBAVectorFromZoneProperty now supports #RRGGBBAA and #RRGGBB format
- owner for all, default 0
- 3.1.2 - getAllZoneProperties has numbersOnly option
- 3.1.3 - new numberArrayFromString()
- new declutterZone()
- new getZoneVolume()
- offsetZone also updates zone bounds when moving zones
- corrected bug in calculateZoneBounds()
- 4.0.0 - dmlZone OOP API started
- code revision / refactoring
- moved createPoint and copxPoint to dcsCommon, added bridging code
@ -66,6 +41,9 @@ cfxZones.version = "4.0.10"
- createPolyZone now correctly inits dcsOrigin
- createCircleZone noew correctly inits dcsOrigin
- 4.0.10 - getBoolFromZoneProperty also supports "on" (=true) and "off" (=false)
- 4.1.0 - getBoolFromZoneProperty 'on/off' support for dml variant as well
- 4.1.1 - evalRemainder() updates
--]]--
--
@ -99,40 +77,6 @@ cfxZones.ups = 1 -- updates per second. updates moving zones
cfxZones.zones = {} -- these are the zone as retrieved from the mission.
-- ALWAYS USE THESE, NEVER DCS's ZONES!!!!
-- a zone has the following attributes
-- x, z -- coordinate of center. note they have correct x, 0, z coordinates so no y-->z mapping
-- radius (zero if quad zone)
-- isCircle (true if quad zone)
-- poly the quad coords are in the poly attribute and are a
-- 1..n, wound counter-clockwise as (currently) in DCS:
-- lower left, lower right upper left, upper right, all coords are x, 0, z
-- bounds - contain the AABB coords for the zone: ul (upper left), ur, ll (lower left), lr
-- for both circle and poly, all (x, 0, z)
-- zones can carry information in their names that can get processed into attributes
-- use
-- zones can also carry information in their 'properties' tag that ME allows to
-- edit. cfxZones provides an easy method to access these properties
-- - getZoneProperty (returns as string)
-- - getMinMaxFromZoneProperty
-- - getBoolFromZoneProperty
-- - getNumberFromZoneProperty
-- SUPPORTED PROPERTIES
-- - "linkedUnit" - zone moves with unit of that name. must be exact match
-- can be combined with other attributes that extend (e.g. scar manager and
-- limited pilots/airframes
--
--
-- readZonesFromDCS is executed exactly once at the beginning
-- from then on, use only the cfxZones.zones table
-- WARNING: cfxZones is NOT case-sensitive. All zone names are
-- indexed by upper case. If you have two zones with same name but
-- different case, one will be replaced
--
function cfxZones.readFromDCS(clearfirst)
if (clearfirst) then
cfxZones.zones = {}
@ -618,8 +562,6 @@ function cfxZones.createRandomZoneInZone(name, inZone, targetRadius, entirelyIns
-- inZone.
-- entirelyInside is not guaranteed for polyzones
-- trigger.action.outText("Zones: creating rZiZ with tr = " .. targetRadius .. " for " .. inZone.name .. " that as r = " .. inZone.radius, 10)
if inZone.isCircle then
local sourceRadius = inZone.radius
if entirelyInside and targetRadius > sourceRadius then targetRadius = sourceRadius end
@ -1364,11 +1306,11 @@ end
-- creating units in a zone
function cfxZones.createGroundUnitsInZoneForCoalition (theCoalition, groupName, theZone, theUnits, formation, heading)
function cfxZones.createGroundUnitsInZoneForCoalition (theCoalition, groupName, theZone, theUnits, formation, heading, liveries)
-- theUnits can be string or table of string
if not groupName then groupName = "G_"..theZone.name end
-- group name will be taken from zone name and prependend with "G_"
local theGroup = dcsCommon.createGroundGroupWithUnits(groupName, theUnits, theZone.radius, nil, formation)
local theGroup = dcsCommon.createGroundGroupWithUnits(groupName, theUnits, theZone.radius, nil, formation, nil, liveries)
-- turn the entire formation to heading
if (not heading) then heading = 0 end
@ -1380,7 +1322,6 @@ function cfxZones.createGroundUnitsInZoneForCoalition (theCoalition, groupName,
theZone.point.x,
theZone.point.z) -- watchit: Z!!!
-- create the group in the world and return it
-- first we need to translate the coalition to a legal
-- country. we use UN for neutral, cjtf for red and blue
@ -1446,7 +1387,7 @@ function cfxZones.unPulseFlag(args)
cfxZones.setFlagValue(theFlag, newVal, theZone)
end
function cfxZones.evalRemainder(remainder)
function cfxZones.evalRemainder(remainder, theZone)
local rNum = tonumber(remainder)
if not rNum then
-- we use remainder as name for flag
@ -1475,6 +1416,10 @@ function cfxZones.evalRemainder(remainder)
return rNum
end
function dmlZone:evalRemainder(remainder)
return cfxZones.evalRemainder(remainder, self)
end
function cfxZones.doPollFlag(theFlag, method, theZone) -- no OOP equivalent
-- WARNING:
-- if method is a number string, it will be interpreted as follows:
@ -2381,7 +2326,7 @@ function cfxZones.getBoolFromZoneProperty(theZone, theProperty, defaultVal)
if defaultVal == false then
-- only go true if exact match to yes or true
theBool = false
theBool = (p == 'true') or (p == 'yes') or (p == "1") or (p == "on")
theBool = (p == 'true') or (p == 'yes') or (p == "1") or (p == 'on')
return theBool
end
@ -2407,13 +2352,13 @@ function dmlZone:getBoolFromZoneProperty(theProperty, defaultVal)
if defaultVal == false then
-- only go true if exact match to yes or true
theBool = false
theBool = (p == 'true') or (p == 'yes') or p == "1"
theBool = (p == 'true') or (p == 'yes') or (p == "1") or (p=="on")
return theBool
end
local theBool = true
-- only go false if exactly no or false or "0"
theBool = (p ~= 'false') and (p ~= 'no') and (p ~= "0")
theBool = (p ~= 'false') and (p ~= 'no') and (p ~= "0") and (p ~= "off")
return theBool
end
@ -3576,8 +3521,6 @@ function cfxZones.init()
-- pre-read zone owner for all zones
-- much like verbose, all zones have owner
-- local pZones = cfxZones.zonesWithProperty("owner")
-- for n, aZone in pairs(pZones) do
for n, aZone in pairs(cfxZones.zones) do
aZone.owner = cfxZones.getCoalitionFromZoneProperty(aZone, "owner", 0)
end

View File

@ -1,140 +0,0 @@
cfxmon = {}
cfxmon.version = "1.0.1"
cfxmon.delay = 30 -- seconds for display
--[[--
Version History
1.0.0 - initial version
1.0.1 - better guard for even.initiator to check if unit exists
- and handle static objcects and other non-grouped objects
cfxmon is a monitor for all cfx events and callbacks
use monConfig to tell cfxmon which events and callbacks
to monitor. a Property with "no" or "false" will turn
that monitor OFF, else it will stay on
supported modules if loaded
dcsCommon
cfxPlayer
cfxGroundTroops
cfxObjectDestructDetector
cfxSpawnZones
--]]--
--
-- CALLBACKS
--
-- dcsCommon Callbacks
function cfxmon.pre(event)
trigger.action.outText("***mon - dcsPre: " .. event.id .. " (" .. dcsCommon.event2text(event.id) .. ")", cfxmon.delay)
return true
end
function cfxmon.post(event)
trigger.action.outText("***mon - dcsPost: " .. event.id .. " (" .. dcsCommon.event2text(event.id) .. ")", cfxmon.delay)
end
function cfxmon.rejected(event)
trigger.action.outText("***mon - dcsReject: " .. event.id .. " (" .. dcsCommon.event2text(event.id) .. ")", cfxmon.delay)
end
function cfxmon.dcsCB(event) -- callback
local initiatorStat = ""
if event.initiator and Unit.isExist(event.initiator) then
local theUnit = event.initiator
-- we assume it is unit, but it can be a static object,
-- and may not have getGroup implemented!
if theUnit.getGroup then
local theGroup = theUnit:getGroup()
local theGroupName = "<none>"
if theGroup then theGroupName = theGroup:getName() end
initiatorStat = ", for " .. theUnit:getName()
initiatorStat = initiatorStat .. " of " .. theGroupName
else
initiatorStat = ", non-unit (static?) " .. theUnit:getName()
end
else
initiatorStat = ", NO Initiator"
end
trigger.action.outText("***mon - dcsMAIN: " .. event.id .. " (" .. dcsCommon.event2text(event.id) .. ")" .. initiatorStat, cfxmon.delay)
end
-- cfxPlayer callback
function cfxmon.playerEventCB(evType, description, info, data)
trigger.action.outText("***mon - cfxPlayer: ".. evType ..": <" .. description .. ">", cfxmon.delay)
end
-- cfxGroundTroops callback
function cfxmon.groundTroopsCB(reason, theGroup, orders, data)
trigger.action.outText("***mon - groundTroops: ".. reason ..": for group <" .. theGroup:getName() .. "> with orders " .. orders, cfxmon.delay)
end
-- object destruct callbacks
function cfxmon.oDestructCB(zone, ObjectID, name)
trigger.action.outText("***mon - object destroyed: ".. ObjectID .." named <" .. name .. "> in zone " .. zone.name, cfxmon.delay)
end
-- spawner callback
function cfxmon.spawnZoneCB(reason, theGroup, theSpawner)
local gName = "<nil>"
if theGroup then gName = theGroup:getName() end
trigger.action.outText("***mon - Spawner: ".. reason .." group <" .. gName .. "> in zone " .. theSpawner.name, cfxmon.delay)
end
-- READ CONFIG AND SUBSCRIBE
function cfxmon.start ()
local theZone = cfxZones.getZoneByName("monConfig")
if not theZone then
trigger.action.outText("***mon: WARNING: NO config, defaulting", cfxmon.delay)
theZone = cfxZones.createSimpleZone("MONCONFIG")
end
-- own config
cfxmon.delay = cfxZones.getNumberFromZoneProperty(theZone, "delay", 30)
trigger.action.outText("!!!mon: Delay is set to: " .. cfxmon.delay .. "seconds", 50)
-- dcsCommon
if cfxZones.getBoolFromZoneProperty(theZone, "dcsCommon", true) then
-- subscribe to dcs event handlers
-- note we have all, but only connect the main
dcsCommon.addEventHandler(cfxmon.dcsCB) -- we only connect one
trigger.action.outText("!!!mon: +dcsCommon", cfxmon.delay)
else
trigger.action.outText("***mon: -dcsCommon", cfxmon.delay)
end
-- cfxPlayer
if cfxPlayer and cfxZones.getBoolFromZoneProperty(theZone, "cfxPlayer", true) then
cfxPlayer.addMonitor(cfxmon.playerEventCB)
trigger.action.outText("!!!mon: +cfxPlayer", cfxmon.delay)
else
trigger.action.outText("***mon: -cfxPlayer", cfxmon.delay)
end
-- cfxGroundTroops
if cfxGroundTroops and cfxZones.getBoolFromZoneProperty(theZone, "cfxGroundTroops", true) then
cfxGroundTroops.addTroopsCallback(cfxmon.groundTroopsCB)
trigger.action.outText("!!!mon: +cfxGroundTroops", cfxmon.delay)
else
trigger.action.outText("***mon: -cfxGroundTroops", cfxmon.delay)
end
-- objectDestructZones
if cfxObjectDestructDetector and cfxZones.getBoolFromZoneProperty(theZone, "cfxObjectDestructDetector", true) then
cfxObjectDestructDetector.addCallback(cfxmon.oDestructCB)
trigger.action.outText("!!!mon: +cfxObjectDestructDetector", cfxmon.delay)
else
trigger.action.outText("***mon: -cfxObjectDestructDetector", cfxmon.delay)
end
-- spawnZones
if cfxSpawnZones and cfxZones.getBoolFromZoneProperty(theZone, "cfxSpawnZones", true) then
cfxSpawnZones.addCallback(cfxmon.spawnZoneCB)
trigger.action.outText("!!!mon: +cfxSpawnZones", cfxmon.delay)
else
trigger.action.outText("***mon: -cfxSpawnZones", cfxmon.delay)
end
end
cfxmon.start()

View File

@ -1,5 +1,5 @@
cloneZones = {}
cloneZones.version = "1.9.1"
cloneZones.version = "2.0.1"
cloneZones.verbose = false
cloneZones.requiredLibs = {
"dcsCommon", -- always
@ -27,79 +27,9 @@ cloneZones.respawnOnGroupID = true
--[[--
Clones Groups from ME mission data
Copyright (c) 2022 by Christian Franz and cf/x AG
Copyright (c) 2022-2024 by Christian Franz and cf/x AG
Version History
1.0.0 - initial version
1.0.1 - preWipe attribute
1.1.0 - support for static objects
- despawn? attribute
1.1.1 - despawnAll: isExist guard
- map in? to f?
1.2.0 - Lua API integration: callbacks
- groupXlate struct
- unitXlate struct
- resolveReferences
- getGroupsInZone rewritten for data
- static resolve
- linkUnit resolve
- clone? synonym
- empty! and method attributes
1.3.0 - DML flag upgrade
1.3.1 - groupTracker interface
- trackWith: attribute
1.4.0 - Watchflags
1.4.1 - trackWith: accepts list of trackers
1.4.2 - onstart delays for 0.1 s to prevent static stacking
- turn bug for statics (bug in dcsCommon, resolved)
1.4.3 - embark/disembark now works with cloners
1.4.4 - removed some debugging verbosity
1.4.5 - randomizeLoc, rndLoc keyword
- cargo manager integration - pass cargo objects when present
1.4.6 - removed some verbosity for spawned aircraft with airfields on their routes
1.4.7 - DML watchflag and DML Flag polish, method-->cloneMethod
1.4.8 - added 'wipe?' synonym
1.4.9 - onRoad option
- rndHeading option
1.5.0 - persistence
1.5.1 - fixed static data cloning bug (load & save)
1.5.2 - fixed bug in trackWith: referencing wrong cloner
1.5.3 - centerOnly/wholeGroups attribute for rndLoc, rndHeading and onRoad
1.5.4 - parking for aircraft processing when cloning from template
1.5.5 - removed some verbosity
1.6.0 - fixed issues with cloning for zones with linked units
- cloning with useHeading
- major declutter
1.6.1 - removed some verbosity when not rotating routes
- updateTaskLocations ()
- cloning groups now also adjusts tasks like search and engage in zone
- cloning with rndLoc supports polygons
- corrected rndLoc without centerOnly to not include individual offsets
- ensure support of recovery tanker resolve cloned group
1.6.2 - optimization to hasLiveUnits()
1.6.3 - removed verbosity bug with rndLoc
uniqueNameGroupData has provisions for naming scheme
new uniqueNameStaticData() for naming scheme
1.6.4 - uniqueCounter is now configurable via config zone
new lclUniqueCounter config, also added to persistence
new globalCount
1.7.0 - wildcard "*" for masterOwner
- identical attribute: makes identical ID and name for unit and group as template
- new sameIDUnitData()
- nameScheme attribute: allow parametric names
- <o>, <z>, <s> wildcards
- <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
1.7.2 - onPerimeter attribute
1.7.3 - declutter option
1.8.0 - OOP cfxZones
- removed "empty+1" as planned
- upgraded config zone parsing
1.8.1 - clone zone definition now supports quads
1.8.2 - on pre-wipe, delay respawn by 0.5s to avoid 'dropping' statics
1.9.0 - minor clean-up for synonyms
- spawnWithSpawner alias for HeloTroops etc requestable SPAWN
- requestable attribute
@ -107,6 +37,9 @@ cloneZones.respawnOnGroupID = true
- cloner collects all types used
- groupScheme attribute
1.9.1 - useAI attribute
2.0.0 - clean-up
2.0.1 - improved empty! logic to account for deferred spawn
when pre-wipe is active
--]]--
--
@ -136,16 +69,6 @@ function cloneZones.addCallback(theCallback)
table.insert(cloneZones.callbacks, theCallback)
end
-- reasons for callback
-- "will despawn group" - args is the group about to be despawned
-- "did spawn group" -- args is group that was spawned
-- "will despawn static"
-- "did spawn static"
-- "spawned" -- completed spawn cycle. args contains .groups and .statics spawned
-- "empty" -- all spawns have been killed, args is empty
-- "wiped" -- preWipe executed
-- "<none" -- something went wrong
function cloneZones.invokeCallbacks(theZone, reason, args)
if not theZone then return end
if not reason then reason = "<none>" end
@ -158,8 +81,6 @@ function cloneZones.invokeCallbacks(theZone, reason, args)
end
end
-- group translation orig id
--
-- reading zones
--
@ -217,7 +138,6 @@ function cloneZones.createClonerWithZone(theZone) -- has "Cloner"
theZone.cloner = true -- this is a cloner zoner
theZone.mySpawns = {}
theZone.myStatics = {}
-- update: getPoint is bad if it's a moving zone.
-- use getDCSOrigin instead
theZone.origin = theZone:getDCSOrigin()
@ -247,7 +167,6 @@ function cloneZones.createClonerWithZone(theZone) -- has "Cloner"
-- iterate all units and save their individual types
for idy, aUnit in pairs(rawData.units) do
local theType = aUnit.type
-- trigger.action.outText("proccing type <" .. theType .. ">", 30)
if not theZone.allTypes[theType] then
theZone.allTypes[theType] = 1 -- first one
else
@ -318,11 +237,8 @@ function cloneZones.createClonerWithZone(theZone) -- has "Cloner"
theZone.cooldown = theZone:getNumberFromZoneProperty("cooldown", -1) -- anything > 0 activates cd
theZone.lastSpawnTimeStamp = -10000
theZone.onStart = theZone:getBoolFromZoneProperty("onStart", false)
theZone.moveRoute = theZone:getBoolFromZoneProperty("moveRoute", false)
theZone.preWipe = theZone:getBoolFromZoneProperty("preWipe", false)
if theZone:hasProperty("empty!") then
@ -383,7 +299,6 @@ function cloneZones.createClonerWithZone(theZone) -- has "Cloner"
theZone.rndHeading = theZone:getBoolFromZoneProperty("rndHeading", false)
theZone.onRoad = theZone:getBoolFromZoneProperty("onRoad", false)
theZone.onPerimeter = theZone:getBoolFromZoneProperty("onPerimeter", false)
-- check for name scheme and / or identical
@ -419,8 +334,6 @@ function cloneZones.despawnAll(theZone)
trigger.action.outText("+++clnZ: despawn all - wiping zone <" .. theZone.name .. ">", 30)
end
for idx, aGroup in pairs(theZone.mySpawns) do
--trigger.action.outText("++clnZ: despawn all " .. aGroup.name, 30)
if aGroup:isExist() then
if theZone.verbose then
trigger.action.outText("+++clnZ: will destroy <" .. aGroup:getName() .. ">", 30)
@ -638,9 +551,6 @@ function cloneZones.nameFromSchema(schema, inName, theZone, sourceName, i)
pos = string.find(outName, "<g>")
end
--if theZone.verbose then
-- trigger.action.outText("+++cln: schema [" .. schema .. "] for unit <" .. inName .. "> in zone <" .. theZone.name .. "> returns <" .. outName .. ">, iter = " .. iter, 30)
--end
return outName, iter
end
@ -866,7 +776,6 @@ function cloneZones.resolveWPReferences(rawData, theZone, dataTable)
for grpIdx, gID in pairs(embarkers) do
local resolvedID = cloneZones.resolveGroupID(gID, rawData, dataTable, "embark")
table.insert(newEmbarkers, resolvedID)
--trigger.action.outText("+++clnZ: resolved embark group id <" .. gID .. "> to <" .. resolvedID .. ">", 30)
end
-- replace old with new table
taskData.params.groupsForEmbarking = newEmbarkers
@ -884,7 +793,6 @@ function cloneZones.resolveWPReferences(rawData, theZone, dataTable)
-- translate old to new
local resolvedID = cloneZones.resolveGroupID(gID, rawData, dataTable, "embark")
table.insert(newEmbarkers, resolvedID)
--trigger.action.outText("+++clnZ: resolved distribute unit/group id <" .. aUnit .. "/" .. gID .. "> to <".. newUnit .. "/" .. resolvedID .. ">", 30)
end
-- store this as new group for
-- translated transportID
@ -892,7 +800,6 @@ function cloneZones.resolveWPReferences(rawData, theZone, dataTable)
end
-- replace old distribution with new
taskData.params.distribution = newDist
--trigger.action.outText("+++clnZ: rebuilt distribution", 30)
end
-- resolve selectedTransport unit reference
@ -900,7 +807,6 @@ function cloneZones.resolveWPReferences(rawData, theZone, dataTable)
local tID = taskData.params.selectedTransport
local newTID = cloneZones.resolveUnitID(tID, rawData, dataTable, "transportID")
taskData.params.selectedTransport = newTID
--trigger.action.outText("+++clnZ: resolved selected transport <" .. tID .. "> to <" .. newTID .. ">", 30)
end
-- note: we may need to process x and y as well
@ -932,7 +838,6 @@ function cloneZones.resolveReferences(theZone, dataTable)
-- when an action refers to another group, we check if
-- the group referred to is also a clone, and update
-- the reference to the newest incardnation
for idx, rawData in pairs(dataTable) do
-- resolve references in waypoints
cloneZones.resolveWPReferences(rawData, theZone, dataTable)
@ -946,7 +851,6 @@ function cloneZones.handoffTracking(theGroup, theZone)
return
end
local trackerName = theZone.trackWith
--if trackerName == "*" then trackerName = theZone.name end
-- now assemble a list of all trackers
if cloneZones.verbose or theZone.verbose then
trigger.action.outText("+++clnZ: clone pass-off: " .. trackerName, 30)
@ -1065,8 +969,6 @@ function cloneZones.forcedRespawn(args)
if newGroupID == theData.CZTargetID then
if verbose then
trigger.action.outText("GOOD REPLACEMENT new ID <" .. newGroupID .. "> matches target <" .. theData.CZTargetID .. "> for <" .. theData.name .. ">", 30)
-- we can now remove the former group
-- and replace it with the new one
trigger.action.outText("will replace table entry at <" .. pos .. "> with new group", 30)
end
spawnedGroups[pos] = theGroup
@ -1104,7 +1006,7 @@ function cloneZones.spawnWithTemplateForZone(theZone, spawnZone)
local newCenter = spawnZone:getPoint() -- includes zone following updates
local oCenter = theZone:getDCSOrigin() -- get original coords on map for cloning offsets
-- calculate zoneDelta, is added to all vectors
local zoneDelta = dcsCommon.vSub(newCenter, theZone.origin) -- oCenter) --theZone.origin)
local zoneDelta = dcsCommon.vSub(newCenter, theZone.origin)
-- precalc turn value for linked rotation
local dHeading = 0 -- for linked zones
@ -1280,7 +1182,6 @@ function cloneZones.spawnWithTemplateForZone(theZone, spawnZone)
-- make group, unit[1] and route point [1] all match up
if rawData.route and rawData.units[1] then
-- trigger.action.outText("matching route point 1 and group with unit 1", 30)
rawData.route.points[1].x = rawData.units[1].x
rawData.route.points[1].y = rawData.units[1].y
rawData.x = rawData.units[1].x
@ -1439,7 +1340,6 @@ function cloneZones.spawnWithTemplateForZone(theZone, spawnZone)
if not spawnZone.identical then
-- make sure static name is unique and remember original
cloneZones.uniqueNameStaticData(rawData, spawnZone, theZone.name)
--rawData.name = dcsCommon.uuid(rawData.name)
rawData.unitId = cloneZones.uniqueID()
end
rawData.CZTargetID = rawData.unitId
@ -1529,7 +1429,6 @@ function cloneZones.turnOffAI(args)
local theGroup = args[1]
local theController = theGroup:getController()
theController:setOnOff(false)
-- trigger.action.outText("turned off AI for group <" .. theGroup:getName() .. "> ", 30)
end
-- retro-fit for helo troops and others to provide 'requestable' support
@ -1721,7 +1620,6 @@ function cloneZones.getRequestableClonersInRange(aPoint, aRange, aSide)
if aSide ~= 0 then
-- check if side is correct for owned zone
local resolved = cloneZones.resolveOwningCoalition(aZone)
--if resolved ~= 0 and resolved ~= aSide then
if resolved == 0 or resolved ~= aSide then
-- failed ownership test. must match and not be zero
hasMatch = false
@ -1749,7 +1647,7 @@ function cloneZones.update()
for idx, aZone in pairs(cloneZones.cloners) do
-- see if deSpawn was pulled. Must run before spawn
if aZone.deSpawnFlag then
local currTriggerVal = aZone:getFlagValue(aZone.deSpawnFlag) -- trigger.misc.getUserFlag(aZone.deSpawnFlag)
local currTriggerVal = aZone:getFlagValue(aZone.deSpawnFlag)
if currTriggerVal ~= aZone.lastDeSpawnValue then
if cloneZones.verbose or aZone.verbose then
trigger.action.outText("+++clnZ: DEspawn triggered for <" .. aZone.name .. ">", 30)
@ -1760,20 +1658,23 @@ function cloneZones.update()
end
-- see if we got spawn? command
local willSpawn = false -- init to false.
if aZone:testZoneFlag(aZone.spawnFlag, aZone.cloneTriggerMethod, "lastSpawnValue") then
if cloneZones.verbose then
if cloneZones.verbose or aZone.verbose then
trigger.action.outText("+++clnZ: spawn triggered for <" .. aZone.name .. ">", 30)
end
cloneZones.spawnWithCloner(aZone)
willSpawn = true -- in case prewipe, we delay
-- can mess with empty, so we tell empty to skip
end
-- empty handling
local isEmpty = cloneZones.countLiveUnits(aZone) < 1 and aZone.hasClones
if isEmpty then
if isEmpty and (willSpawn == false) then
-- see if we need to bang a flag
if aZone.emptyBangFlag then
aZone:pollFlag(aZone.emptyBangFlag, aZone.cloneMethod)
if cloneZones.verbose then
if cloneZones.verbose or aZone.verbose then
trigger.action.outText("+++clnZ: bang! on " .. aZone.emptyBangFlag, 30)
end
end
@ -1983,7 +1884,6 @@ function cloneZones.loadData()
trigger.action.outText("+++clnZ: linked static <" .. oName .. "> to unit <" .. newStatic.linkUnit .. ">", 30)
end
local cty = newStatic.cty
-- local cat = staticData.cat
-- spawn new one, replacing same.named old, dead if required
gStatic = coalition.addStaticObject(cty, newStatic)
@ -2004,7 +1904,7 @@ function cloneZones.loadData()
for cName, cData in pairs(allCloners) do
local theCloner = cloneZones.getCloneZoneByName(cName)
if theCloner then
theCloner.isStarted = true -- ALWAYS TRUE WHEN WE COME HERE! cData.isStarted
theCloner.isStarted = true
-- init myUniqueCounter if it exists
if cData.myUniqueCounter then
theCloner.myUniqueCounter = cData.myUniqueCounter
@ -2095,9 +1995,6 @@ function cloneZones.start()
-- process cloner Zones
local attrZones = cfxZones.getZonesWithAttributeNamed("cloner")
-- now create an rnd gen for each one and add them
-- to our watchlist
for k, aZone in pairs(attrZones) do
cloneZones.createClonerWithZone(aZone) -- process attribute and add to zone
cloneZones.addCloneZone(aZone)

View File

@ -1,5 +1,5 @@
countDown = {}
countDown.version = "1.3.2"
countDown.version = "2.0.0"
countDown.verbose = false
countDown.ups = 1
countDown.requiredLibs = {
@ -9,26 +9,14 @@ countDown.requiredLibs = {
--[[--
count down on flags to generate new signal on out
Copyright (c) 2022, 2023 by Christian Franz and cf/x AG
Copyright (c) 2022 - 2024 by Christian Franz and cf/x AG
Version History
1.0.0 - initial version
1.1.0 - Lua interface: callbacks
- corrected verbose (erroneously always suppressed)
- triggerFlag --> triggerCountFlag
1.1.1 - corrected bug in invokeCallback
1.2.0 - DML Flags
- counterOut!
- ups config
1.2.1 - disableCounter?
1.3.0 - DML & Watchflags upgrade
- method --> ctdwnMethod
1.3.1 - clock? synonym
- new reset? input
- improved verbosity
- zone-local verbosity
1.3.2 - enableCounter? to balande disableCounter?
2.0.0 - dmlZones, OOP upgrade
counterOut! --> counterOut#
output method defaults to "inc"
better config parsing
cleanup
--]]--
countDown.counters = {}
@ -77,7 +65,7 @@ end
--
function countDown.createCountDownWithZone(theZone)
-- start val - a range
theZone.startMinVal, theZone.startMaxVal = cfxZones.getPositiveRangeFromZoneProperty(theZone, "countDown", 1) -- we know this exists
theZone.startMinVal, theZone.startMaxVal = theZone:getPositiveRangeFromZoneProperty("countDown", 1) -- we know this exists
theZone.currVal = dcsCommon.randomBetween(theZone.startMinVal, theZone.startMaxVal)
if countDown.verbose then
@ -85,32 +73,32 @@ function countDown.createCountDownWithZone(theZone)
end
-- loop
theZone.loop = cfxZones.getBoolFromZoneProperty(theZone, "loop", false)
theZone.loop = theZone:getBoolFromZoneProperty("loop", false)
-- extend after zero
theZone.belowZero = cfxZones.getBoolFromZoneProperty(theZone, "belowZero", false)
theZone.belowZero = theZone:getBoolFromZoneProperty("belowZero", false)
-- out method
theZone.ctdwnMethod = cfxZones.getStringFromZoneProperty(theZone, "method", "flip")
if cfxZones.hasProperty(theZone, "ctdwnMethod") then
theZone.ctdwnMethod = cfxZones.getStringFromZoneProperty(theZone, "ctdwnMethod", "flip")
theZone.ctdwnMethod = theZone:getStringFromZoneProperty("method", "inc")
if theZone:hasProperty("ctdwnMethod") then
theZone.ctdwnMethod = theZone:getStringFromZoneProperty( "ctdwnMethod", "inc")
end
-- triggerMethod for inputs
theZone.ctdwnTriggerMethod = cfxZones.getStringFromZoneProperty(theZone, "triggerMethod", "change")
theZone.ctdwnTriggerMethod = theZone:getStringFromZoneProperty( "triggerMethod", "change")
if cfxZones.hasProperty(theZone, "ctdwnTriggerMethod") then
theZone.ctdwnTriggerMethod = cfxZones.getStringFromZoneProperty(theZone, "ctdwnTriggerMethod", "change")
if theZone:hasProperty("ctdwnTriggerMethod") then
theZone.ctdwnTriggerMethod = theZone:getStringFromZoneProperty("ctdwnTriggerMethod", "change")
end
-- trigger flag "count" / "start?"
if cfxZones.hasProperty(theZone, "count?") then
theZone.triggerCountFlag = cfxZones.getStringFromZoneProperty(theZone, "count?", "<none>")
elseif cfxZones.hasProperty(theZone, "clock?") then
theZone.triggerCountFlag = cfxZones.getStringFromZoneProperty(theZone, "clock?", "<none>")
if theZone:hasProperty("count?") then
theZone.triggerCountFlag = theZone:getStringFromZoneProperty("count?", "<none>")
elseif theZone:hasProperty("clock?") then
theZone.triggerCountFlag = theZone:getStringFromZoneProperty("clock?", "<none>")
-- can also use in? for counting. we always use triggerCountFlag
elseif cfxZones.hasProperty(theZone, "in?") then
theZone.triggerCountFlag = cfxZones.getStringFromZoneProperty(theZone, "in?", "<none>")
elseif theZone:hasProperty("in?") then
theZone.triggerCountFlag = theZone:getStringFromZoneProperty("in?", "<none>")
end
if theZone.triggerCountFlag then
@ -118,40 +106,40 @@ function countDown.createCountDownWithZone(theZone)
end
-- reset
if cfxZones.hasProperty(theZone, "reset?") then
theZone.resetFlag = cfxZones.getStringFromZoneProperty(theZone, "reset?", "<none>")
if theZone:hasProperty("reset?") then
theZone.resetFlag = theZone:getStringFromZoneProperty("reset?", "<none>")
theZone.resetFlagValue = cfxZones.getFlagValue(theZone.resetFlag, theZone)
end
-- zero! bang
if cfxZones.hasProperty(theZone, "zero!") then
theZone.zeroFlag = cfxZones.getStringFromZoneProperty(theZone, "zero!", "<none>")
if theZone:hasProperty("zero!") then
theZone.zeroFlag = theZone:getStringFromZoneProperty("zero!", "<none>")
end
if cfxZones.hasProperty(theZone, "out!") then
theZone.zeroFlag = cfxZones.getStringFromZoneProperty(theZone, "out!", "<none>")
if theZone:hasProperty("out!") then
theZone.zeroFlag = theZone:getStringFromZoneProperty("out!", "<none>")
end
-- TMinus! bang
if cfxZones.hasProperty(theZone, "tMinus!") then
theZone.tMinusFlag = cfxZones.getStringFromZoneProperty(theZone, "tMinus!", "<none>")
if theZone:hasProperty("tMinus!") then
theZone.tMinusFlag = theZone:getStringFromZoneProperty("tMinus!", "<none>")
end
-- counterOut val
if cfxZones.hasProperty(theZone, "counterOut!") then
theZone.counterOut = cfxZones.getStringFromZoneProperty(theZone, "counterOut!", "<none>")
if theZone:hasProperty("counterOut#") then
theZone.counterOut = theZone:getStringFromZoneProperty( "counterOut#", "<none>")
end
-- disableFlag/enableFlag
theZone.counterDisabled = false
if cfxZones.hasProperty(theZone, "disableCounter?") then
theZone.disableCounterFlag = cfxZones.getStringFromZoneProperty(theZone, "disableCounter?", "<none>")
theZone.disableCounterFlagVal = cfxZones.getFlagValue(theZone.disableCounterFlag, theZone)
if theZone:hasProperty("disableCounter?") then
theZone.disableCounterFlag = theZone:getStringFromZoneProperty("disableCounter?", "<none>")
theZone.disableCounterFlagVal = theZone:getFlagValue(theZone.disableCounterFlag)
end
if cfxZones.hasProperty(theZone, "enableCounter?") then
theZone.enableCounterFlag = cfxZones.getStringFromZoneProperty(theZone, "enableCounter?", "<none>")
theZone.enableCounterFlagVal = cfxZones.getFlagValue(theZone.enableCounterFlag, theZone)
if theZone:hasProperty("enableCounter?") then
theZone.enableCounterFlag = theZone:getStringFromZoneProperty("enableCounter?", "<none>")
theZone.enableCounterFlagVal = theZone:getFlagValue(theZone.enableCounterFlag)
end
end
@ -170,7 +158,7 @@ function countDown.reset(theZone)
cfxZones.setFlagValue(theZone.counterOut, val, theZone)
end
-- read and ignore any pulling of the clock flag
local ignore = cfxZones.testZoneFlag(theZone, theZone.triggerCountFlag, theZone.ctdwnTriggerMethod, "lastCountTriggerValue")
local ignore = theZone:testZoneFlag(theZone.triggerCountFlag, theZone.ctdwnTriggerMethod, "lastCountTriggerValue")
-- simply updates lastTriggerValue to current clock value
end
@ -186,7 +174,7 @@ function countDown.isTriggered(theZone)
local looping = false
if theZone.counterOut then
cfxZones.setFlagValue(theZone.counterOut, val, theZone)
theZone:setFlagValue(theZone.counterOut, val)
end
if val > 0 then
@ -196,7 +184,7 @@ function countDown.isTriggered(theZone)
if countDown.verbose or theZone.verbose then
trigger.action.outText("+++cntD: <" .. theZone.name .. "> TMINUTS on flag <" .. theZone.tMinusFlag .. ">", 30)
end
cfxZones.pollFlag(theZone.tMinusFlag, theZone.ctdwnMethod, theZone)
theZone:pollFlag(theZone.tMinusFlag, theZone.ctdwnMethod)
end
elseif val == 0 then
@ -206,7 +194,7 @@ function countDown.isTriggered(theZone)
if countDown.verbose or theZone.verbose then
trigger.action.outText("+++cntD: ZERO <" .. theZone.name .. "> on flag <" .. theZone.zeroFlag .. ">", 30)
end
cfxZones.pollFlag(theZone.zeroFlag, theZone.ctdwnMethod, theZone)
theZone:pollFlag(theZone.zeroFlag, theZone.ctdwnMethod)
end
if theZone.loop then
@ -225,7 +213,7 @@ function countDown.isTriggered(theZone)
if countDown.verbose or theZone.verbose then
trigger.action.outText("+++cntD: Below Zero", 30)
end
cfxZones.pollFlag(theZone.zeroFlag, theZone.ctdwnMethod, theZone)
theZone:pollFlag(theZone.zeroFlag, theZone.ctdwnMethod)
end
end
@ -243,7 +231,7 @@ function countDown.update()
for idx, aZone in pairs(countDown.counters) do
if aZone.resetFlag then
if cfxZones.testZoneFlag(aZone, aZone.resetFlag, aZone.ctdwnTriggerMethod, "resetFlagValue") then
if aZone:testZoneFlag(aZone.resetFlag, aZone.ctdwnTriggerMethod, "resetFlagValue") then
-- reset pulled, reset the timer to start condition
countDown.reset(aZone)
end
@ -252,7 +240,7 @@ function countDown.update()
-- make sure to re-start before reading time limit
-- if reset, lastTriggerValue is updated and will not trigger
if (not aZone.counterDisabled) and
cfxZones.testZoneFlag(aZone, aZone.triggerCountFlag, aZone.ctdwnTriggerMethod, "lastCountTriggerValue")
aZone:testZoneFlag(aZone.triggerCountFlag, aZone.ctdwnTriggerMethod, "lastCountTriggerValue")
then
if countDown.verbose then
trigger.action.outText("+++cntD: triggered on in?", 30)
@ -260,14 +248,14 @@ function countDown.update()
countDown.isTriggered(aZone)
end
if cfxZones.testZoneFlag(aZone, aZone.disableCounterFlag, aZone.ctdwnTriggerMethod, "disableCounterFlagVal") then
if aZone:testZoneFlag(aZone.disableCounterFlag, aZone.ctdwnTriggerMethod, "disableCounterFlagVal") then
if countDown.verbose then
trigger.action.outText("+++cntD: disabling counter " .. aZone.name, 30)
end
aZone.counterDisabled = true
end
if cfxZones.testZoneFlag(aZone, aZone.enableCounterFlag, aZone.ctdwnTriggerMethod, "enableCounterFlagVal") then
if aZone:testZoneFlag(aZone.enableCounterFlag, aZone.ctdwnTriggerMethod, "enableCounterFlagVal") then
if countDown.verbose then
trigger.action.outText("+++cntD: ENabling counter " .. aZone.name, 30)
end
@ -282,16 +270,13 @@ end
function countDown.readConfigZone()
local theZone = cfxZones.getZoneByName("countDownConfig")
if not theZone then
if countDown.verbose then
trigger.action.outText("+++cntD: NO config zone!", 30)
end
return
theZone = cfxZones.createSimpleZone("countDownConfig")
end
countDown.ups = cfxZones.getNumberFromZoneProperty(theZone, "ups", 1)
countDown.ups = theZone:getNumberFromZoneProperty("ups", 1)
if countDown.ups < 0.001 then countDown.ups = 0.001 end
countDown.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false)
countDown.verbose = theZone.verbose
if countDown.verbose then
trigger.action.outText("+++cntD: read config", 30)

View File

@ -1,183 +1,13 @@
dcsCommon = {}
dcsCommon.version = "2.9.8"
dcsCommon.version = "3.0.0"
--[[-- VERSION HISTORY
2.2.6 - compassPositionOfARelativeToB
- clockPositionOfARelativeToB
2.2.7 - isTroopCarrier
- distFlat
2.2.8 - fixed event2text
2.2.9 - getUnitAGL
- getUnitAlt
- getUnitSpeed
- getUnitHeading
- getUnitHeadingDegrees
- mag
- clockPositionOfARelativeToB with own heading
2.3.0 - unitIsInfantry
2.3.1 - bool2YesNo
- bool2Text
2.3.2 - getGroupAvgSpeed
- getGroupMaxSpeed
2.3.3 - getSizeOfTable
2.3.4 - isSceneryObject
coalition2county
2.3.5 - smallRandom
pickRandom uses smallRandom
airfield handling, parking
flight waypoint handling
landing waypoint creation
take-off waypoint creation
2.3.6 - createOverheadAirdromeRoutPintData(aerodrome)
2.3.7 - coalition2county - warning when creating UN
2.3.8 - improved headingOfBInDegrees, new getClockDirection
2.3.9 - getClosingVelocity
- dot product
- magSquare
- vMag
2.4.0 - libCheck
2.4.1 - grid/square/rect formation
- arrangeGroupInNColumns formation
- 2Columns formation deep and wide formation
2.4.2 - getAirbasesInRangeOfPoint
2.4.3 - lerp
2.4.4 - getClosestAirbaseTo
- fixed bug in containsString when strings equal
2.4.5 - added cargo and mass options to createStaticObjectData
2.4.6 - fixed randompercent
2.4.7 - smokeColor2Num(smokeColor)
2.4.8 - linkStaticDataToUnit()
2.4.9 - trim functions
- createGroundUnitData uses trim function to remove leading/trailing blanks
so now we can use blanks after comma to separate types
- dcsCommon.trimArray(
- createStaticObjectData uses trim for type
- getEnemyCoalitionFor understands strings, still returns number
- coalition2county also understands 'red' and 'blue'
2.5.0 - "Line" formation with one unit places unit at center
2.5.1 - vNorm(a)
2.5.1 - added SA-18 Igla manpad to unitIsInfantry()
2.5.2 - added copyArray method
- corrected heading in createStaticObjectData
2.5.3 - corrected rotateGroupData bug for cz
- removed forced error in failed pickRandom
2.5.4 - rotateUnitData()
- randomBetween()
2.5.5 - stringStartsWithDigit()
- stringStartsWithLetter()
- stringIsPositiveNumber()
2.5.6 - corrected stringEndsWith() bug with str
2.5.7 - point2text(p)
2.5.8 - string2GroupCat()
2.5.9 - string2ObjectCat()
2.6.0 - unified uuid, removed uuIdent
2.6.1 - removed bug in rotateUnitData: cy --> cz param passing
2.6.2 - new combineTables()
2.6.3 - new tacan2freq()
2.6.4 - new processHMS()
2.6.5 - new bearing2compass()
- new bearingdegrees2compass()
- new latLon2Text() - based on mist
2.6.6 - new nowString()
- new str2num()
- new stringRemainsStartingWith()
- new stripLF()
- new removeBlanks()
2.6.7 - new menu2text()
2.6.8 - new getMissionName()
- new flagArrayFromString()
2.6.9 - new getSceneryObjectsInZone()
- new getSceneryObjectInZoneByName()
2.7.0 - new synchGroupData()
clone, topClone and copyArray now all nil-trap
2.7.1 - new isPlayerUnit() -- moved from cfxPlayer
new getAllExistingPlayerUnitsRaw - from cfxPlayer
new typeIsInfantry()
2.7.2 - new rangeArrayFromString()
fixed leading blank bug in flagArrayFromString
new incFlag()
new decFlag()
nil trap in stringStartsWith()
new getClosestFreeSlotForCatInAirbaseTo()
2.7.3 - new string2Array()
- additional guard for isPlayerUnit
2.7.4 - new array2string()
2.7.5 - new bitAND32()
- new LSR()
- new num2bin()
2.7.6 - new getObjectsForCatAtPointWithRadius()
2.7.7 - clone() has new stripMeta option. pass true to remove all meta tables
- dumpVar2Str detects meta tables
- rotateGroupData kills unit's psi value if it existed since it messes with heading
- rotateGroupData - changes psi to -heading if it exists rather than nilling
2.7.8 - new getGeneralDirection()
- new getNauticalDirection()
- more robust guards for getUnitSpeed
2.7.9 - new bool2Num(theBool)
- new aspectByDirection()
- createGroundGroupWithUnits corrected spelling of minDist, crashed scattered formation
- randomPointInCircle fixed erroneous local for x, z
- "scattered" formation repaired
2.7.10- semaphore groundwork
2.8.0 - new collectMissionIDs at start-up
- new getUnitNameByID
- 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
2.8.2 - better fixes for string.find() in stringStartsWith and containsString
- dcsCommon.isTroopCarrier(theUnit, carriers) new carriers optional param
- better guards for getUnitAlt and getUnitAGL
- new newPointAtDegreesRange()
- new newPointAtAngleRange()
- new isTroopCarrierType()
- stringStartsWith now supports case insensitive match
- isTroopCarrier() supports 'any' and 'all'
- made getEnemyCoalitionFor() more resilient
- fix to smallRandom for negative numbers
- isTroopCarrierType uses wildArrayContainsString
2.8.3 - small optimizations in bearingFromAtoB()
- new whichSideOfMine()
2.8.4 - new rotatePointAroundOriginRad()
- new rotatePointAroundPointDeg()
- new rotatePointAroundPointRad()
- getClosestAirbaseTo() now supports passing list of air bases
2.8.5 - better guard in getGroupUnit()
2.8.6 - phonetic helpers
new spellString()
2.8.7 - new flareColor2Num()
- new flareColor2Text()
- new iteratePlayers()
2.8.8 - new hexString2RGBA()
- new playerName2Coalition()
- new coalition2Text()
2.8.9 - vAdd supports xy and xyz
- vSub supports xy and xyz
- vMultScalar supports xy and xyz
2.8.10 - tacan2freq now integrated with module (blush)
- array2string cosmetic default
- vMultScalar corrected bug in accessing b.z
- new randomLetter()
- new getPlayerUnit()
- new getMapName()
- new getMagDeclForPoint()
2.9.0 - createPoint() moved from cfxZones
- copyPoint() moved from cfxZones
- numberArrayFromString() moved from cfxZones
2.9.1 - new createSimpleRoutePointData()
- createOverheadAirdromeRoutPintData corrected and legacy support added
- new bearingFromAtoBusingXY()
- corrected verbosity for bearingFromAtoB
- new getCountriesForCoalition()
2.9.2 - updated event2text
2.9.3 - getAirbasesWhoseNameContains now supports category tables for filtering
2.9.4 - new bearing2degrees()
2.9.5 - distanceOfPointPToLineXZ(p, p1, p2)
2.9.6 - new addToTableIfNew()
2.9.7 - createSimpleRoutePointData also accepts speed
2.9.8 - isSceneryObject(theUnit) optimization, DCS 2.9 safe
3.0.0 - removed bad bug in stringStartsWith, only relevant if caseSensitive is false
- point2text new intsOnly option
- arrangeGroupDataIntoFormation minDist harden
- cleanup
- new pointInDirectionOfPointXYY()
- createGroundGroupWithUnits now supports liveries
- new getAllExistingPlayersAndUnits()
--]]--
-- dcsCommon is a library of common lua functions
@ -511,7 +341,6 @@ dcsCommon.version = "2.9.8"
-- a table containing categories, e.g. {0, 2} = airfields and ships but not farps
-- if no name given or aName = "*", then all bases are returned prior to filtering
function dcsCommon.getAirbasesWhoseNameContains(aName, filterCat, filterCoalition)
--trigger.action.outText("getAB(name): enter with " .. aName, 30)
if not aName then aName = "*" end
local allYourBase = world.getAirbases() -- get em all
local areBelongToUs = {}
@ -520,9 +349,6 @@ dcsCommon.version = "2.9.8"
local airBaseName = aBase:getName() -- get display name
if aName == "*" or dcsCommon.containsString(airBaseName, aName) then
-- containsString is case insesitive unless told otherwise
--if aName ~= "*" then
-- trigger.action.outText("getAB(name): matched " .. airBaseName, 30)
--end
local doAdd = true
if filterCat then
local aCat = dcsCommon.getAirbaseCat(aBase)
@ -628,13 +454,11 @@ dcsCommon.version = "2.9.8"
for idx, aSlot in pairs(reallyFree) do
local sp = {x = aSlot.vTerminalPos.x, y = 0, z = aSlot.vTerminalPos.z}
local currDist = dcsCommon.distFlat(p, sp)
--trigger.action.outText("slot <" .. aSlot.Term_Index .. "> has dist " .. math.floor(currDist) .. " and _0 of <" .. aSlot.Term_Index_0 .. ">", 30)
if currDist < closestDist then
closestSlot = aSlot
closestDist = currDist
end
end
--trigger.action.outText("slot <" .. closestSlot.Term_Index .. "> has closest dist <" .. math.floor(closestDist) .. ">", 30)
return closestSlot
end
@ -888,12 +712,9 @@ dcsCommon.version = "2.9.8"
if not A then return "***error:A***" end
if not B then return "***error:B***" end
if not headingOfBInDegrees then headingOfBInDegrees = 0 end
local bearing = dcsCommon.bearingInDegreesFromAtoB(B, A) -- returns 0..360
-- trigger.action.outText("+++comm: oclock - bearing = " .. bearing .. " and inHeading = " .. headingOfBInDegrees, 30)
bearing = bearing - headingOfBInDegrees
return dcsCommon.getClockDirection(bearing)
end
-- given a heading, return clock with 0 being 12, 180 being 6 etc.
@ -1079,7 +900,6 @@ dcsCommon.version = "2.9.8"
end
-- if we get here, there was no live unit
--trigger.action.outText("+++cmn: A group has no live units. returning nil", 10)
return nil
end
@ -1107,7 +927,6 @@ dcsCommon.version = "2.9.8"
end
-- if we get here, there was no live unit
--trigger.action.outText("+++cmn A group has no live units. returning nil", 10)
return nil
end
@ -1233,7 +1052,6 @@ dcsCommon.version = "2.9.8"
-- when filtering occurs in pre, an alternative 'rejected' handler can be called
function dcsCommon.addEventHandler(f, pre, post, rejected) -- returns ID
local handler = {} -- build a wrapper and connect the onEvent
--dcsCommon.cbID = dcsCommon.cbID + 1 -- increment unique count
handler.id = dcsCommon.uuid("eventHandler")
handler.f = f -- the callback itself
if (rejected) then handler.rejected = rejected end
@ -1245,7 +1063,6 @@ dcsCommon.version = "2.9.8"
function handler:onEvent(event)
if not self.pre(event) then
if dcsCommon.verbose then
-- trigger.action.outText("event " .. event.id .. " discarded by pre-processor", 10)
end
if (self.rejected) then self.rejected(event) end
return
@ -1428,8 +1245,8 @@ dcsCommon.version = "2.9.8"
rp.action = "Turning Point"
rp.type = "Turning Point"
if action then rp.action = action; rp.type = action end -- warning: may not be correct, need to verify later
rp.alt = altitudeInFeet * 0.3048
rp.speed = knots * 0.514444 -- we use
rp.alt = altitudeInFeet * 0.3048 -- in m
rp.speed = knots * 0.514444 -- we use m/s
rp.alt_type = "BARO"
if (altType) then rp.alt_type = altType end
return rp
@ -1485,7 +1302,7 @@ dcsCommon.version = "2.9.8"
rp.action = "From Parking Area"
rp.type = "TakeOffParking"
rp.speed = 100; -- in m/s? If so, that's 360 km/h
rp.speed = 100 -- that's 360 km/h
rp.alt_type = "BARO"
return rp
end
@ -1673,7 +1490,6 @@ dcsCommon.version = "2.9.8"
-- now do the formation stuff
-- make sure that they keep minimum distance
-- trigger.action.outText("dcsCommon - processing formation " .. formation .. " with radius = " .. radius, 30)
if formation == "LINE_V" then
-- top to bottom in zone (heding 0). -- will run through x-coordinate
-- use entire radius top to bottom
@ -1682,7 +1498,6 @@ dcsCommon.version = "2.9.8"
for i=1, num do
local u = theNewGroup.units[i]
-- trigger.action.outText("formation unit " .. u.name .. " currX = " .. currX, 30)
u.x = currX
currX = currX + increment
end
@ -1698,7 +1513,6 @@ dcsCommon.version = "2.9.8"
local increment = radius * 2/(num - 1) -- MUST NOT TRY WITH 1 UNIT!
for i=1, num do
local u = theNewGroup.units[i]
-- trigger.action.outText("formation unit " .. u.name .. " currX = " .. currY, 30)
u.y = currY
currY = currY + increment
end
@ -1713,7 +1527,6 @@ dcsCommon.version = "2.9.8"
local incrementX = radius * 2/(num - 1) -- MUST NOT TRY WITH 1 UNIT!
for i=1, num do
local u = theNewGroup.units[i]
-- trigger.action.outText("formation unit " .. u.name .. " currX = " .. currX .. " currY = " .. currY, 30)
u.x = currX
u.y = currY
-- calc coords for NEXT iteration
@ -1731,6 +1544,7 @@ dcsCommon.version = "2.9.8"
elseif formation == "SCATTERED" or formation == "RANDOM" then
-- use randomPointInCircle and tehn iterate over all vehicles for mindelta
processedUnits = {}
if not minDist then minDist = 10 end
for i=1, num do
local emergencyBreak = 1 -- prevent endless loop
local lowDist = 10000
@ -1760,7 +1574,6 @@ dcsCommon.version = "2.9.8"
elseif dcsCommon.stringStartsWith(formation, "CIRCLE") then
-- units are arranged on perimeter of circle defined by radius
-- trigger.action.outText("formation circle detected", 30)
local currAngle = 0
local angleInc = 2 * 3.14157 / num -- increase per spoke
for i=1, num do
@ -1786,40 +1599,7 @@ dcsCommon.version = "2.9.8"
-- calculate w
local w = math.floor(num^(0.5) + 0.5)
dcsCommon.arrangeGroupInNColumns(theNewGroup, w, radius)
--[[--
local h = math.floor(num / w)
--trigger.action.outText("AdcsC: num=" .. num .. " w=" .. w .. "h=" .. h .. " -- num%w=" .. num%w, 30)
if (num % w) > 0 then
h = h + 1
end
--trigger.action.outText("BdcsC: num=" .. num .. " w=" .. w .. "h=" .. h, 30)
-- now w * h always >= num and num items fir in that grid
-- w is width, h is height, of course :)
-- now calculat xInc and yInc
local i = 1
local xInc = 0
if w > 1 then xInc = 2 * radius / (w-1) end
local yInc = 0
if h > 1 then yInc = 2 * radius / (h-1) end
local currY = radius
if h < 2 then currY = 0 end -- special:_ place in Y middle if only one row)
while h > 0 do
local currX = radius
local wCnt = w
while wCnt > 0 and (i <= num) do
local u = theNewGroup.units[i] -- get unit
u.x = currX
u.y = currY
currX = currX - xInc
wCnt = wCnt - 1
i = i + 1
end
currY = currY - yInc
h = h - 1
end
--]]--
elseif formation == "2DEEP" or formation == "2COLS" then
if num < 2 then return end
-- arrange units in an 2 x h grid
@ -1896,11 +1676,14 @@ dcsCommon.version = "2.9.8"
end
function dcsCommon.createGroundGroupWithUnits(name, theUnitTypes, radius, minDist, formation, innerRadius)
function dcsCommon.createGroundGroupWithUnits(name, theUnitTypes, radius, minDist, formation, innerRadius, liveries)
-- liveries is indexed by typeName and provides alternate livery names
-- from default.
if not minDist then minDist = 4 end -- meters
if not formation then formation = "line" end
if not radius then radius = 30 end -- meters
if not innerRadius then innerRadius = 0 end
if not liveries then liveries = {} end
formation = formation:upper()
-- theUnitTypes can be either a single string or a table of strings
-- see here for TypeName https://github.com/mrSkortch/DCS-miscScripts/tree/master/ObjectDB
@ -1920,13 +1703,8 @@ dcsCommon.version = "2.9.8"
-- now add a single unit or multiple units
if type(theUnitTypes) ~= "table" then
-- trigger.action.outText("dcsCommon - i am here", 30)
-- trigger.action.outText("dcsCommon - name " .. name, 30)
-- trigger.action.outText("dcsCommon - unit type " .. theUnitTypes, 30)
local aUnit = {}
aUnit = dcsCommon.createGroundUnitData(name .. "-1", theUnitTypes, false)
-- trigger.action.outText("dcsCommon - unit name retval " .. aUnit.name, 30)
dcsCommon.addUnitToGroupData(aUnit, theNewGroup, 0, 0) -- create with data at location (0,0)
return theNewGroup
end
@ -1937,6 +1715,10 @@ dcsCommon.version = "2.9.8"
for key, theType in pairs(theUnitTypes) do
-- trigger.action.outText("+++dcsC: creating unit " .. name .. "-" .. num .. ": " .. theType, 30)
local aUnit = dcsCommon.createGroundUnitData(name .. "-"..num, theType, false)
local theLivery = liveries[theType]
if theLivery then
aUnit.livery_id = theLivery
end
dcsCommon.addUnitToGroupData(aUnit, theNewGroup, 0, 0)
num = num + 1
end
@ -1991,15 +1773,23 @@ dcsCommon.version = "2.9.8"
elseif cat == Group.Category.GROUND then
-- we got all we need
else
-- trigger.action.outText("dcsCommon - unknown category: " .. cat, 30)
-- return nil
-- we also got all we need
end
end
end;
function dcsCommon.pointInDirectionOfPointXYY(dir, dist, p) -- dir in rad, p in XYZ returns XYY
local fx = math.cos(dir)
local fy = math.sin(dir)
local p2 = {}
p2.x = p.x + dist * fx
p2.y = p.z + dist * fy
p2.z = p2.y -- make p2 XYY vec2/3 upcast
return p2
end
function dcsCommon.rotatePointAroundOriginRad(inX, inY, angle) -- angle in degrees
local c = math.cos(angle)
local s = math.sin(angle)
@ -2046,7 +1836,6 @@ dcsCommon.version = "2.9.8"
if not cx then cx = 0 end
if not cz then cz = 0 end
local cy = cz
--trigger.action.outText("+++dcsC:rotGrp cy,cy = "..cx .. "," .. cy, 30)
local rads = degrees * 3.14152 / 180
do
@ -2066,7 +1855,6 @@ dcsCommon.version = "2.9.8"
if not cx then cx = 0 end
if not cz then cz = 0 end
local cy = cz
--trigger.action.outText("+++dcsC:rotGrp cy,cy = "..cx .. "," .. cy, 30)
local rads = degrees * 3.14152 / 180
-- turns all units in group around the group's center by degrees.
@ -2080,9 +1868,6 @@ dcsCommon.version = "2.9.8"
-- may also want to increase heading by degrees
theUnit.heading = theUnit.heading + rads
-- now kill psi if it existed before
-- theUnit.psi = nil
-- better code: psi is always -heading. Nobody knows what psi is, though
if theUnit.psi then
theUnit.psi = -theUnit.heading
end
@ -2247,33 +2032,27 @@ end
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 idx, theElement in pairs(theArray) do -- i = 1, #theArray do
--local theElement = theArray[i]
--trigger.action.outText("test e <" .. theElement .. "> against s <" .. theString .. ">", 30)
if not 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("startswith - match e* <" .. theElement .. "> with s <" .. theString .. "> 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 theElement == theString then return true end
--trigger.action.outText("match e with s (straight) failed.", 30)
end
end
@ -2412,7 +2191,7 @@ end
if caseInsensitive then
theString = string.upper(theString)
thePrefix = string.upper(theString)
thePrefix = string.upper(thePrefix)
end
-- superseded: string.find (s, pattern [, init [, plain]]) solves the problem
local i, j = string.find(theString, thePrefix, 1, true)
@ -2467,12 +2246,19 @@ end
return 0
end
function dcsCommon.point2text(p)
function dcsCommon.point2text(p, intsOnly)
if not intsOnly then intsOnly = false end
if not p then return "<!NIL!>" end
local t = "[x="
if p.x then t = t .. p.x .. ", " else t = t .. "<nil>, " end
if p.y then t = t .. "y=" .. p.y .. ", " else t = t .. "y=<nil>, " end
if p.z then t = t .. "z=" .. p.z .. "]" else t = t .. "z=<nil>]" end
if intsOnly then
if p.x then t = t .. math.floor(p.x) .. ", " else t = t .. "<nil>, " end
if p.y then t = t .. "y=" .. math.floor(p.y) .. ", " else t = t .. "y=<nil>, " end
if p.z then t = t .. "z=" .. math.floor(p.z) .. "]" else t = t .. "z=<nil>]" end
else
if p.x then t = t .. p.x .. ", " else t = t .. "<nil>, " end
if p.y then t = t .. "y=" .. p.y .. ", " else t = t .. "y=<nil>, " end
if p.z then t = t .. "z=" .. p.z .. "]" else t = t .. "z=<nil>]" end
end
return t
end
@ -2605,7 +2391,6 @@ end
if not inrecursion then
-- output a marker to find in the log / screen
trigger.action.outText("=== dcsCommon vardump end", 30)
--env.info("=== dcsCommon vardump end")
end
end
@ -2636,7 +2421,9 @@ end
"BDA", "AI Abort Mission", "DayNight", "Flight Time", -- 40
"Pilot Suicide", "player cap airfield", "emergency landing", "unit create task", -- 44
"unit delete task", "Simulation start", "weapon rearm", "weapon drop", -- 48
"unit task timeout", "unit task stage",
"unit task timeout", "unit task stage", -- 50
"subtask score", "extra score", "mission restart", "winner",
"postponed takeoff", "postponed land", -- 56
"max"}
if id > #events then return "Unknown (ID=" .. id .. ")" end
return events[id]
@ -2926,6 +2713,21 @@ function dcsCommon.getAllExistingPlayerUnitsRaw()
return apu
end
function dcsCommon.getAllExistingPlayersAndUnits() -- units indexed by player name
-- designed to replace cases for cfxPlayer.getAllPlayer invocations
local apu = {}
for idx, theSide in pairs(dcsCommon.coalitionSides) do
local thePlayers = coalition.getPlayers(theSide)
for idy, theUnit in pairs (thePlayers) do
if theUnit and theUnit:isExist() then
local pName = theUnit:getPlayerName()
apu[pName] = theUnit
end
end
end
return apu
end
function dcsCommon.getUnitAlt(theUnit)
if not theUnit then return 0 end
if not Unit.isExist(theUnit) then return 0 end -- safer
@ -3028,16 +2830,6 @@ function dcsCommon.unitIsInfantry(theUnit)
if not theUnit then return false end
if not theUnit:isExist() then return end
local theType = theUnit:getTypeName()
--[[--
local isInfantry =
dcsCommon.containsString(theType, "infantry", false) or
dcsCommon.containsString(theType, "paratrooper", false) or
dcsCommon.containsString(theType, "stinger", false) or
dcsCommon.containsString(theType, "manpad", false) or
dcsCommon.containsString(theType, "soldier", false) or
dcsCommon.containsString(theType, "SA-18 Igla", false)
return isInfantry
--]]--
return dcsCommon.typeIsInfantry(theType)
end

View File

@ -1,5 +1,5 @@
delayFlag = {}
delayFlag.version = "1.4.0"
delayFlag.version = "2.0.0"
delayFlag.verbose = false
delayFlag.requiredLibs = {
"dcsCommon", -- always
@ -11,35 +11,12 @@ delayFlag.flags = {}
delay flags - simple flag switch & delay, allows for randomize
and dead man switching
Copyright (c) 2022 by Christian Franz and cf/x AG
Copyright (c) 2022-2024 by Christian Franz and cf/x AG
Version History
1.0.0 - Initial Version
1.0.1 - message attribute
1.0.2 - slight spelling correction
- using cfxZones for polling
- removed pollFlag
1.0.3 - bug fix for config zone name
- removed message attribute, moved to own module
- triggerFlag --> triggerDelayFlag
1.0.4 - startDelay
1.1.0 - DML flag upgrade
- removed onStart. use local raiseFlag instead
- delayDone! synonym
- pauseDelay?
- unpauseDelay?
1.2.0 - Watchflags
1.2.1 - method goes to dlyMethod
- delay done is correctly inited
1.2.2 - delayMethod defaults to inc
- zone-local verbosity
- code clean-up
1.2.3 - pauseDelay
- continueDelay
- delayLeft
1.3.0 - persistence
1.4.0 - dmlZones
- delayLeft#
2.0.0 - clean-up
--]]--
@ -317,7 +294,7 @@ function delayFlag.readConfigZone()
theZone = cfxZones.createSimpleZone("delayFlagsConfig")
end
delayFlag.verbose = theZone.verbose -- cfxZones.getBoolFromZoneProperty(theZone, "verbose", false)
delayFlag.verbose = theZone.verbose
if delayFlag.verbose then
trigger.action.outText("+++dlyF: read config", 30)

View File

@ -1,5 +1,5 @@
factoryZone = {}
factoryZone.version = "2.0.0"
factoryZone.version = "3.0.0"
factoryZone.verbose = false
factoryZone.name = "factoryZone"
@ -9,6 +9,8 @@ factoryZone.name = "factoryZone"
- "production" and "defenders" simplification
- now optional specification for red/blue
- use maxRadius from zone for spawning to support quad zones
3.0.0 - support for liveries via "factoryLiveries" zone
- OOP dmlZones
--]]--
factoryZone.requiredLibs = {
@ -20,6 +22,7 @@ factoryZone.requiredLibs = {
}
factoryZone.zones = {} -- my factory zones
factoryZone.liveries = {} -- indexed by type name
factoryZone.ups = 1
factoryZone.initialized = false
factoryZone.defendingTime = 100 -- seconds until new defenders are produced
@ -28,7 +31,7 @@ factoryZone.shockTime = 200 -- 'shocked' period of inactivity
factoryZone.repairTime = 200 -- time until we raplace one lost unit, also repairs all other units to 100%
-- persistence: all attackers we ever sent out.
-- is regularly verified and cut to size
-- is regularly verified and cut to size via GC
factoryZone.spawnedAttackers = {}
-- factoryZone is a module that manages production of units
@ -38,24 +41,6 @@ factoryZone.spawnedAttackers = {}
--
-- *** EXTENTDS ZONES ***
--
-- zone attributes when owned
-- owner: coalition that owns the zone. Managed externally
-- status: FSM for spawning
-- defendersRED/BLUE - coma separated type string for the group to spawn on defense cycle completion
-- attackersRED/BLUE - as above for attack cycle.
-- timeStamp - time when zone switched into current state
-- spawnRadius - overrides zone's radius when placing defenders. can be use to place defenders inside or outside zone itself
-- formation - defender's formation
-- attackFormation - attackers formation
-- attackRadius - radius of circle in which attackers are spawned. informs formation
-- attackDelta - polar coord: r from zone center where attackers are spawned
-- attackPhi - polar degrees where attackers are to be spawned
-- paused - will not spawn. default is false
--
-- M I S C
--
function factoryZone.getFactoryZoneByName(zName)
for zKey, theZone in pairs (factoryZone.zones) do
@ -65,59 +50,58 @@ function factoryZone.getFactoryZoneByName(zName)
end
function factoryZone.addFactoryZone(aZone)
--aZone.worksFor = cfxZones.getCoalitionFromZoneProperty(aZone, "factory", 0) -- currently unused, have RED/BLUE separate types
aZone.state = "init"
aZone.timeStamp = timer.getTime()
-- set up production default
local factory = cfxZones.getStringFromZoneProperty(aZone, "factory", "none")
local factory = aZone:getStringFromZoneProperty("factory", "none")
local production = cfxZones.getStringFromZoneProperty(aZone, "production", factory)
local production = aZone:getStringFromZoneProperty("production", factory)
local defenders = cfxZones.getStringFromZoneProperty(aZone, "defenders", factory)
local defenders = aZone:getStringFromZoneProperty("defenders", factory)
if cfxZones.hasProperty(aZone, "attackersRED") then
if aZone:hasProperty("attackersRED") then
-- legacy support
aZone.attackersRED = cfxZones.getStringFromZoneProperty(aZone, "attackersRED", production)
aZone.attackersRED = aZone:getStringFromZoneProperty( "attackersRED", production)
else
aZone.attackersRED = cfxZones.getStringFromZoneProperty(aZone, "productionRED", production)
aZone.attackersRED = aZone:getStringFromZoneProperty( "productionRED", production)
end
if cfxZones.hasProperty(aZone, "attackersBLUE") then
if aZone:hasProperty("attackersBLUE") then
-- legacy support
aZone.attackersBLUE = cfxZones.getStringFromZoneProperty(aZone, "attackersBLUE", production)
aZone.attackersBLUE = aZone:getStringFromZoneProperty( "attackersBLUE", production)
else
aZone.attackersBLUE = cfxZones.getStringFromZoneProperty(aZone, "productionBLUE", production)
aZone.attackersBLUE = aZone:getStringFromZoneProperty( "productionBLUE", production)
end
-- set up defenders default, or use production / factory
aZone.defendersRED = cfxZones.getStringFromZoneProperty(aZone, "defendersRED", defenders)
aZone.defendersBLUE = cfxZones.getStringFromZoneProperty(aZone, "defendersBLUE", defenders)
aZone.defendersRED = aZone:getStringFromZoneProperty("defendersRED", defenders)
aZone.defendersBLUE = aZone:getStringFromZoneProperty("defendersBLUE", defenders)
aZone.formation = cfxZones.getStringFromZoneProperty(aZone, "formation", "circle_out")
aZone.attackFormation = cfxZones.getStringFromZoneProperty(aZone, "attackFormation", "circle_out") -- cfxZones.getZoneProperty(aZone, "attackFormation")
aZone.spawnRadius = cfxZones.getNumberFromZoneProperty(aZone, "spawnRadius", aZone.maxRadius-5) -- "-5" so they remaininside radius
aZone.attackRadius = cfxZones.getNumberFromZoneProperty(aZone, "attackRadius", aZone.maxRadius)
aZone.attackDelta = cfxZones.getNumberFromZoneProperty(aZone, "attackDelta", 10) -- aZone.radius)
aZone.attackPhi = cfxZones.getNumberFromZoneProperty(aZone, "attackPhi", 0)
aZone.formation = aZone:getStringFromZoneProperty("formation", "circle_out")
aZone.attackFormation = aZone:getStringFromZoneProperty( "attackFormation", "circle_out") -- cfxZones.getZoneProperty(aZone, "attackFormation")
aZone.spawnRadius = aZone:getNumberFromZoneProperty("spawnRadius", aZone.maxRadius-5) -- "-5" so they remaininside radius
aZone.attackRadius = aZone:getNumberFromZoneProperty("attackRadius", aZone.maxRadius)
aZone.attackDelta = aZone:getNumberFromZoneProperty("attackDelta", 10) -- aZone.radius)
aZone.attackPhi = aZone:getNumberFromZoneProperty("attackPhi", 0)
aZone.paused = cfxZones.getBoolFromZoneProperty(aZone, "paused", false)
aZone.paused = aZone:getBoolFromZoneProperty("paused", false)
aZone.factoryOwner = aZone.owner -- copy so we can compare next round
-- pause? and activate?
if cfxZones.hasProperty(aZone, "pause?") then
aZone.pauseFlag = cfxZones.getStringFromZoneProperty(aZone, "pause?", "none")
if aZone:hasProperty("pause?") then
aZone.pauseFlag = aZone:getStringFromZoneProperty("pause?", "none")
aZone.lastPauseValue = trigger.misc.getUserFlag(aZone.pauseFlag)
end
if cfxZones.hasProperty(aZone, "activate?") then
aZone.activateFlag = cfxZones.getStringFromZoneProperty(aZone, "activate?", "none")
if aZone:hasProperty("activate?") then
aZone.activateFlag = aZone:getStringFromZoneProperty("activate?", "none")
aZone.lastActivateValue = trigger.misc.getUserFlag(aZone.activateFlag)
end
aZone.factoryTriggerMethod = cfxZones.getStringFromZoneProperty(aZone, "triggerMethod", "change")
if cfxZones.hasProperty(aZone, "factoryTriggerMethod") then
aZone.factoryTriggerMethod = cfxZones.getStringFromZoneProperty(aZone, "factoryTriggerMethod", "change")
aZone.factoryTriggerMethod = aZone:getStringFromZoneProperty( "triggerMethod", "change")
if aZone:hasProperty("factoryTriggerMethod") then
aZone.factoryTriggerMethod = aZone:getStringFromZoneProperty( "factoryTriggerMethod", "change")
end
factoryZone.zones[aZone.name] = aZone
@ -159,11 +143,12 @@ function factoryZone.spawnAttackTroops(theTypes, aZone, aCoalition, aFormation)
local theGroup, theData = cfxZones.createGroundUnitsInZoneForCoalition (
aCoalition, -- theCountry,
aZone.name .. " (A) " .. dcsCommon.numberUUID(), -- must be unique
aZone.name .. " (A) " .. dcsCommon.numberUUID(),
spawnZone,
unitTypes,
aFormation, -- outward facing
0)
0,
factoryZone.liveries)
return theGroup, theData
end
@ -184,10 +169,12 @@ function factoryZone.spawnDefensiveTroops(theTypes, aZone, aCoalition, aFormatio
local spawnZone = cfxZones.createSimpleZone("spawnZone", aZone.point, aZone.spawnRadius)
local theGroup, theData = cfxZones.createGroundUnitsInZoneForCoalition (
aCoalition, --theCountry,
aZone.name .. " (D) " .. dcsCommon.numberUUID(), -- must be unique
spawnZone, unitTypes,
aZone.name .. " (D) " .. dcsCommon.numberUUID(),
spawnZone,
unitTypes,
aFormation, -- outward facing
0)
0,
factoryZone.liveries)
return theGroup, theData
end
@ -308,7 +295,8 @@ function factoryZone.repairDefenders(aZone)
livingTypes,
aZone.formation, -- outward facing
0)
0,
factoryZone.liveries)
aZone.defenders = theGroup
aZone.lastDefenders = theGroup:getSize()
end
@ -686,15 +674,26 @@ end
function factoryZone.readConfigZone(theZone)
if not theZone then theZone = cfxZones.createSimpleZone("factoryZoneConfig") end
factoryZone.name = "factoryZone" -- just in case, so we can access with cfxZones
factoryZone.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false)
factoryZone.defendingTime = cfxZones.getNumberFromZoneProperty(theZone, "defendingTime", 100)
factoryZone.attackingTime = cfxZones.getNumberFromZoneProperty(theZone, "attackingTime", 300)
factoryZone.shockTime = cfxZones.getNumberFromZoneProperty(theZone, "shockTime", 200)
factoryZone.repairTime = cfxZones.getNumberFromZoneProperty(theZone, "repairTime", 200)
factoryZone.verbose = theZone.verbose
factoryZone.defendingTime = theZone:getNumberFromZoneProperty( "defendingTime", 100)
factoryZone.attackingTime = theZone:getNumberFromZoneProperty( "attackingTime", 300)
factoryZone.shockTime = theZone:getNumberFromZoneProperty("shockTime", 200)
factoryZone.repairTime = theZone:getNumberFromZoneProperty( "repairTime", 200)
factoryZone.targetZones = "OWNED"
end
function factoryZone.readLiveries()
theZone = cfxZones.getZoneByName("factoryLiveries")
if not theZone then return end
factoryZone.liveries = theZone:getAllZoneProperties()
trigger.action.outText("Custom liveries detected. All factories now use:", 30)
for aType, aLivery in pairs (factoryZone.liveries) do
trigger.action.outText(" type <" .. aType .. "> now uses livery <" .. aLivery .. ">", 30)
end
end
function factoryZone.init()
-- check libs
if not dcsCommon.libCheck("cfx Factory Zones",
@ -706,12 +705,12 @@ function factoryZone.init()
local theZone = cfxZones.getZoneByName("factoryZoneConfig")
factoryZone.readConfigZone(theZone)
-- read livery presets for factory production
factoryZone.readLiveries()
-- collect all zones by their 'factory' property
-- start the process
local pZones = cfxZones.zonesWithProperty("factory")
-- now add all zones to my zones table, and convert the owner property into
-- a proper attribute
for k, aZone in pairs(pZones) do
factoryZone.addFactoryZone(aZone)
end

View File

@ -1,5 +1,5 @@
fireFX = {}
fireFX.version = "1.1.0"
fireFX.version = "2.0.1"
fireFX.verbose = false
fireFX.ups = 1
fireFX.requiredLibs = {
@ -15,7 +15,7 @@ fireFX.fx = {}
1.1.1 - agl attribute
2.0.0 - dmlZones OOP
- rndLoc
2.0.1 - fixed rndLoc determination
--]]--
function fireFX.addFX(theZone)
@ -92,9 +92,9 @@ function fireFX.createFXWithZone(theZone)
end
theZone.rndLoc = theZone:getBoolFromZoneProperty("rndLoc", false)
if theZone.max + 1 and (not theZone.rndLoc) then
if theZone.max > 1 and (not theZone.rndLoc) then
if theZone.verbose or fireFX.verbose then
trigger.action.outText("+++ffx: more than 1 fires, will set to random loc")
trigger.action.outText("+++ffx: more than 1 fires, will set to random loc", 30)
end
theZone.rndLoc = true
end
@ -113,7 +113,7 @@ function fireFX.startTheFire(theZone)
theZone.fireNames = {}
local num = cfxZones.randomInRange(theZone.min, theZone.max)
for i = 1, num do
local p = cfxZones.getPoint(theZone)
local p = theZone:getPoint()
if theZone.rndLoc then
p = theZone:randomPointInZone()
end

View File

@ -1,5 +1,5 @@
groupTracker = {}
groupTracker.version = "1.2.1"
groupTracker.version = "2.0.0"
groupTracker.verbose = false
groupTracker.ups = 1
groupTracker.requiredLibs = {
@ -10,28 +10,7 @@ groupTracker.trackers = {}
--[[--
Version History
1.0.0 - Initial version
1.1.0 - filtering added
- array support for trackers
- array support for trackers
1.1.1 - corrected clone zone reference bug
1.1.2 - corrected naming (removed bang from flags), deprecated old
- more zone-local verbosity
1.1.3 - spellings
- addGroupToTrackerNamed bug removed accessing tracker
- new removeGroupNamedFromTrackerNamed()
1.1.4 - destroy? input
- allGone! output
- triggerMethod
- method
- isDead optimiz ation
1.2.0 - double detection
- numUnits output
- persistence
1.2.1 - allGone! bug removed
1.2.2 - new groupTrackedBy() method
- limbo for storing a unit in limbo so it is
- not counted as missing when being transported
2.0.0 - dmlZones, OOP, clean-up, legacy support
--]]--
@ -259,62 +238,7 @@ function groupTracker.removeGroupNamedFromTrackerNamed(gName, trackerName)
end
groupTracker.removeGroupNamedFromTracker(gName, theTracker)
--[[--
local filteredGroups = {}
local foundOne = false
local totalUnits = 0
if not theTracker.trackedGroups then theTracker.trackedGroups = {} end
for idx, aGroup in pairs(theTracker.trackedGroups) do
if aGroup:getName() == gName then
-- skip and remember
foundOne = true
else
table.insert(filteredGroups, aGroup)
if Group.isExist(aGroup) then
totalUnits = totalUnits + aGroup:getSize()
end
end
end
-- also check limbo
for limboName, limboNum in pairs (theTracker.limbo) do
if gName == limboName then
-- don't count, but remember that it existed
foundOne = true
else
totalUnits = totalUnits + limboNum
end
end
-- remove from limbo
theTracker.limbo[gName] = nil
if (not foundOne) and (theTracker.verbose or groupTracker.verbose) then
trigger.action.outText("+++gTrk: Removal Request Note: group <" .. gName .. "> wasn't tracked by <" .. trackerName .. ">", 30)
end
-- remember the new, cleanded set
theTracker.trackedGroups = filteredGroups
-- update number of tracked units. do it in any case
if theTracker.tNumUnits then
cfxZones.setFlagValue(theTracker.tNumUnits, totalUnits, theTracker)
end
if foundOne then
if theTracker.verbose or groupTracker.verbose then
trigger.action.outText("+++gTrk: removed group <" .. gName .. "> from tracker <" .. trackerName .. ">", 30)
end
-- now bang/invoke addGroup!
if theTracker.tRemoveGroup then
cfxZones.pollFlag(theTracker.tRemoveGroup, "inc", theTracker)
end
-- now set numGroups
if theTracker.tNumGroups then
cfxZones.setFlagValue(theTracker.tNumGroups, dcsCommon.getSizeOfTable(theTracker.limbo) + #theTracker.trackedGroups, theTracker)
end
end
--]]--
end
-- groupTrackedBy - return trackers that track group theGroup
@ -365,62 +289,56 @@ function groupTracker.createTrackerWithZone(theZone)
theZone.limbo = {} -- name based, for groups that are tracked
-- although technically off the map (helo etc)
theZone.trackerMethod = cfxZones.getStringFromZoneProperty(theZone, "method", "inc")
if cfxZones.hasProperty(theZone, "trackerMethod") then
theZone.trackerMethod = cfxZones.getStringFromZoneProperty(theZone, "trackerMethod", "inc")
theZone.trackerMethod = theZone:getStringFromZoneProperty("method", "inc")
if theZone:hasProperty("trackerMethod") then
theZone.trackerMethod = theZone:getStringFromZoneProperty( "trackerMethod", "inc")
end
theZone.trackerTriggerMethod = cfxZones.getStringFromZoneProperty(theZone, "triggerMethod", "change")
theZone.trackerTriggerMethod = theZone:getStringFromZoneProperty("triggerMethod", "change")
if cfxZones.hasProperty(theZone, "trackerTriggerMethod") then
theZone.trackerTriggerMethod = cfxZones.getStringFromZoneProperty(theZone, "trackerTriggerMethod", "change")
if theZone:hasProperty("trackerTriggerMethod") then
theZone.trackerTriggerMethod = theZone:getStringFromZoneProperty("trackerTriggerMethod", "change")
end
if cfxZones.hasProperty(theZone, "numGroups") then
theZone.tNumGroups = cfxZones.getStringFromZoneProperty(theZone, "numGroups", "*<none>")
-- we may need to zero this flag
elseif cfxZones.hasProperty(theZone, "numGroups!") then -- DEPRECATED!
theZone.tNumGroups = cfxZones.getStringFromZoneProperty(theZone, "numGroups!", "*<none>")
-- we may need to zero this flag
if theZone:hasProperty("numGroups") then
theZone.tNumGroups = theZone:getStringFromZoneProperty("numGroups", "*<none>") -- legacy support
elseif theZone:hasProperty("numGroups#") then
theZone.tNumGroups = theZone:getStringFromZoneProperty( "numGroups#", "*<none>")
end
if cfxZones.hasProperty(theZone, "numUnits") then
theZone.tNumUnits = cfxZones.getStringFromZoneProperty(theZone, "numUnits", "*<none>")
if theZone:hasProperty("numUnits") then
theZone.tNumUnits = theZOne:getStringFromZoneProperty("numUnits", "*<none>") -- legacy support
elseif theZone:hasProperty("numUnits#") then
theZone.tNumUnits = theZone:getStringFromZoneProperty("numUnits#", "*<none>")
end
if cfxZones.hasProperty(theZone, "addGroup") then
theZone.tAddGroup = cfxZones.getStringFromZoneProperty(theZone, "addGroup", "*<none>")
-- we may need to zero this flag
elseif cfxZones.hasProperty(theZone, "addGroup!") then -- DEPRECATED
theZone.tAddGroup = cfxZones.getStringFromZoneProperty(theZone, "addGroup!", "*<none>")
-- we may need to zero this flag
if theZone:hasProperty("addGroup") then
theZone.tAddGroup = theZone:getStringFromZoneProperty("addGroup", "*<none>") -- legacy support
elseif theZone:hasProperty("addGroup!") then
theZone.tAddGroup = theZone:getStringFromZoneProperty("addGroup!", "*<none>")
end
if cfxZones.hasProperty(theZone, "removeGroup") then
theZone.tRemoveGroup = cfxZones.getStringFromZoneProperty(theZone, "removeGroup", "*<none>")
-- we may need to zero this flag
elseif cfxZones.hasProperty(theZone, "removeGroup!") then -- DEPRECATED!
theZone.tRemoveGroup = cfxZones.getStringFromZoneProperty(theZone, "removeGroup!", "*<none>")
-- we may need to zero this flag
if theZone:hasProperty("removeGroup") then
theZone.tRemoveGroup = theZone:getStringFromZoneProperty( "removeGroup", "*<none>") -- legacy support
elseif theZone:hasProperty("removeGroup!") then
theZone.tRemoveGroup = theZone:getStringFromZoneProperty( "removeGroup!", "*<none>")
end
if cfxZones.hasProperty(theZone, "groupFilter") then
local filterString = cfxZones.getStringFromZoneProperty(theZone, "groupFilter", "2") -- ground
if theZone:hasProperty("groupFilter") then
local filterString = theZone:getStringFromZoneProperty( "groupFilter", "2") -- ground
theZone.groupFilter = dcsCommon.string2GroupCat(filterString)
if groupTracker.verbose or theZone.verbose then
trigger.action.outText("+++gTrck: filtering " .. theZone.groupFilter .. " in " .. theZone.name, 30)
end
end
if cfxZones.hasProperty(theZone, "destroy?") then
theZone.destroyFlag = cfxZones.getStringFromZoneProperty(theZone, "destroy?", "*<none>")
if theZone:hasProperty("destroy?") then
theZone.destroyFlag = theZone:getStringFromZoneProperty(theZone, "destroy?", "*<none>")
theZone.lastDestroyValue = cfxZones.getFlagValue(theZone.destroyFlag, theZone)
end
if cfxZones.hasProperty(theZone, "allGone!") then
theZone.allGoneFlag = cfxZones.getStringFromZoneProperty(theZone, "allGone!", "<None>") -- note string on number default
if theZone:hasProperty("allGone!") then
theZone.allGoneFlag = theZone:getStringFromZoneProperty("allGone!", "<None>") -- note string on number default
end
theZone.lastGroupCount = 0
@ -652,15 +570,12 @@ end
function groupTracker.readConfigZone()
local theZone = cfxZones.getZoneByName("groupTrackerConfig")
if not theZone then
if groupTracker.verbose then
trigger.action.outText("+++gTrk: NO config zone!", 30)
end
return
theZone = cfxZones.createSimpleZone("groupTrackerConfig")
end
groupTracker.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false)
groupTracker.verbose = theZoneverbose
groupTracker.ups = cfxZones.getNumberFromZoneProperty(theZone, "ups", 1)
groupTracker.ups = theZone:getNumberFromZoneProperty("ups", 1)
if groupTracker.verbose then
trigger.action.outText("+++gTrk: read config", 30)

View File

@ -1,5 +1,5 @@
messenger = {}
messenger.version = "2.3.1"
messenger.version = "3.0.0"
messenger.verbose = false
messenger.requiredLibs = {
"dcsCommon", -- always
@ -8,68 +8,9 @@ messenger.requiredLibs = {
messenger.messengers = {}
--[[--
Version History
1.0.0 - initial version
1.0.1 - messageOut? synonym
- spelling types in about
1.1.0 - DML flag support
- clearScreen option
- inValue?
- message preprocessor
1.1.1 - firewalled coalition to msgCoalition
- messageOn?
- messageOff?
1.2.0 - msgTriggerMethod (original Watchflag integration)
1.2.1 - qoL: <n> = newline, <z> = zone name, <v> = value
1.3.0 - messenger? saves messageOut? attribute
1.3.1 - message now can interpret value as time with <h> <m> <s> <:h> <:m> <:s>
1.3.2 - message interprets <t> as time in HH:MM:SS of current time
- can interpret <lat>, <lon>, <mgrs>
- zone-local verbosity
1.3.3 - mute/messageMute option to start messenger in mute
2.0.0 - re-engineered message wildcards
- corrected dynamic content for time and latlon (classic)
- new timeFormat attribute
- <v: flagname>
- <t: flagname>
- added <ele>
- added imperial
- <lat: unit/zone>
- <lon: unit/zone>
- <ele: unit/zone>
- <mgrs: unit/zone>
- <latlon: unit/zone>
- <lle: unit/zone>
- messageError
- unit
- group
2.0.1 - config optimization
2.1.0 - unit only: dynamicUnitProcessing with other units/zones
- <bae: u/z> bearing to unit/zone
- <rbae u/z> response mapped by unit's heading
- <clk: u/z> bearing in clock position to unit/zone
- <rng: u/z> range to unit/zone
- <hnd: u/z> bearing in left/right/ahead/behind
- <sde: u/z> bearing in starboard/port/ahead/aft
- added dynamicGroupProcessing to select unit 1
- responses attribute
- <rsp: flag>
- <rrnd> response randomized
- <rhdg: u/z> respons mapped by unit's heading
- <cls unit> closing speed
- <vel unit> velocity (speed)
- <asp unit> aspect
- 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
2.2.1 - when messenger is linked to a unit, it can use the linked
unit as reference point for relative wildcards. Always broadcasts to coalition. Can be used to broadcase 'eye in the sky' type information
- fixed verbosity bug
2.3.0 - cfxZones OOP switch
2.3.1 - triggering message AFTER the on/off switches are tested
3.0.0 - removed messenger, in?, f? attributes, harmonized on messenger?
--]]--
function messenger.addMessenger(theZone)
@ -255,11 +196,11 @@ function messenger.createMessengerWithZone(theZone)
if theZone:hasProperty("in?") then
theZone.triggerMessagerFlag = theZone:getStringFromZoneProperty("in?", "none")
end
--[[--
if theZone:hasProperty("messageOut?") then
theZone.triggerMessagerFlag = theZone:getStringFromZoneProperty("messageOut?", "none")
end
--]]--
-- try default only if no other is set
if not theZone.triggerMessagerFlag then
if not theZone:hasProperty("messenger?") then
@ -505,12 +446,13 @@ function messenger.start()
-- process messenger Zones
-- old style
--[[--
local attrZones = cfxZones.getZonesWithAttributeNamed("messenger")
for k, aZone in pairs(attrZones) do
messenger.createMessengerWithZone(aZone) -- process attributes
messenger.addMessenger(aZone) -- add to list
end
--]]--
-- new style that saves messageOut? flag by reading flags
attrZones = cfxZones.getZonesWithAttributeNamed("messenger?")
for k, aZone in pairs(attrZones) do

View File

@ -17,6 +17,7 @@ cfxObjectDestructDetector.requiredLibs = {
re-wrote object determination to not be affected by
ID changes (happens with map updates)
fail addZone when name property is missing
2.0.1 check that the object is within the zone onEvent
--]]--
cfxObjectDestructDetector.objectZones = {}
@ -86,6 +87,8 @@ function cfxObjectDestructDetector:onEvent(event)
if event.id == world.event.S_EVENT_DEAD then
if not event.initiator then return end
local theObject = event.initiator
-- check location
local pos = theObject:getPoint()
local desc = theObject:getDesc()
if not desc then return end
local matchMe = desc.typeName -- we home in on object's typeName
@ -93,7 +96,10 @@ function cfxObjectDestructDetector:onEvent(event)
matchMe = string.upper(matchMe)
for idx, aZone in pairs(cfxObjectDestructDetector.objectZones) do
if (not aZone.isDestroyed) and aZone.objName == matchMe then
if (not aZone.isDestroyed)
and aZone.objName == matchMe
and aZone:pointInZone(pos) -- make sure it's not a dupe
then
if aZone.outDestroyFlag then
aZone:pollFlag(aZone.outDestroyFlag, aZone.oddMethod)
end

View File

@ -13,25 +13,6 @@ cfxObjectSpawnZones.verbose = false
*** DOES NOT EXTEND ZONES ***
version history
1.0.0 - based on 1.4.6 version from cfxSpawnZones
1.1.0 - uses linkedUnit, so spawnming can occur on ships
make sure you also enable useOffset to place the
statics away from the center of the ship
1.1.1 - also processes paused flag
- despawnRemaining(spawner)
1.1.2 - autoRemove option re-installed
- added possibility to autoUnlink
1.1.3 - ME-triggered flag via f? and triggerFlag
1.1.4 - activate?, pause? attributes
1.1.5 - spawn?, spawnObjects? synonyms
1.2.0 - DML flag upgrade
1.2.1 - config zone
- autoLink bug (zone instead of spawner accessed)
1.3.0 - better synonym handling
- useDelicates link to delicate when spawned
- spawned single and multi-objects can be made delicates
1.3.1 - baseName can be set to zone's name by giving "*"
1.3.2 - delicateName supports '*' to refer to own zone
2.0.0 - dmlZones
--]]--

View File

@ -1,5 +1,5 @@
persistence = {}
persistence.version = "1.0.7"
persistence.version = "2.0.0"
persistence.ups = 1 -- once every 1 seconds
persistence.verbose = false
persistence.active = false
@ -10,39 +10,25 @@ persistence.saveDir = nil -- set at start
persistence.name = "persistence" -- for cfxZones
persistence.missionData = {} -- loaded from file
persistence.requiredLibs = {
"dcsCommon", -- always
"cfxZones", -- Zones, of course
"dcsCommon",
"cfxZones",
}
--[[--
Version History
1.0.0 - initial version
1.0.1 - when available, module sets flag "cfxPersistence" to 1
- when data availabe, cfxPersistenceHasData is set to 1
- spelling
- cfxZones interface
- always output save location
1.0.2 - QoL when verbosity is on
1.0.3 - no longer always tells " mission saved to"
new 'saveNotification" can be off
1.0.4 - new optional 'root' property
1.0.5 - desanitize check on readConfig to early-abort
1.0.6 - removed potential verbosity bug
1.0.7 - correct abort for sanitized DCS, when non-verbose
2.0.0 - dml zones, OOP
cleanup
PROVIDES LOAD/SAVE ABILITY TO MODULES
PROVIDES STANDALONE/HOSTED SERVER COMPATIBILITY
--]]--
-- in order to work, Host must desanitize lfs and io
-- only works when run as server
-- in order to work, HOST MUST DESANITIZE lfs and io
--
-- flags to save. can be added to by saveFlags attribute
--
persistence.flagsToSave = {} -- simple table
persistence.callbacks = {} -- cbblocks, dictionary
persistence.callbacks = {} -- cbblocks, dictionary by name
--
@ -417,13 +403,13 @@ end
--
function persistence.collectFlagsFromZone(theZone)
local theFlags = cfxZones.getStringFromZoneProperty(theZone, "saveFlags", "*dummy")
local theFlags = theZone:getStringFromZoneProperty("saveFlags", "*dummy")
persistence.registerFlagsToSave(theFlags, theZone)
end
function persistence.readConfigZone()
if not _G["lfs"] then
trigger.action.outText("+++persistence: DCS not correctly desanitized. Persistence disabled", 30)
trigger.action.outText("+++persistence: DCS correctly not 'desanitized'. Persistence disabled", 30)
return
end
@ -436,10 +422,10 @@ function persistence.readConfigZone()
-- serverDir is the path from the server save directory, usually "Missions/".
-- will be added to lfs.writedir() unless given a root attribute
if cfxZones.hasProperty(theZone, "root") then
if theZone:hasProperty("root") then
-- we split this to enable further processing down the
-- line if neccessary
persistence.root = cfxZones.getStringFromZoneProperty(theZone, "root", lfs.writedir()) -- safe default
persistence.root = theZone:getStringFromZoneProperty("root", lfs.writedir()) -- safe default
if not dcsCommon.stringEndsWith(persistence.root, "\\") then
persistence.root = persistence.root .. "\\"
end
@ -453,11 +439,11 @@ function persistence.readConfigZone()
end
end
persistence.serverDir = cfxZones.getStringFromZoneProperty(theZone, "serverDir", "Missions\\")
persistence.serverDir = theZone:getStringFromZoneProperty("serverDir", "Missions\\")
if hasConfig then
if cfxZones.hasProperty(theZone, "saveDir") then
persistence.saveDir = cfxZones.getStringFromZoneProperty(theZone, "saveDir", "")
if theZone:hasProperty("saveDir") then
persistence.saveDir = theZone:getStringFromZoneProperty("saveDir", "")
else
-- local missname = net.dostring_in("gui", "return DCS.getMissionName()") .. " (data)"
persistence.saveDir = dcsCommon.getMissionName() .. " (data)"
@ -472,32 +458,32 @@ function persistence.readConfigZone()
trigger.action.outText("*** WARNING: persistence is set to write to main mission directory!", 30)
end
if cfxZones.hasProperty(theZone, "saveFileName") then
persistence.saveFileName = cfxZones.getStringFromZoneProperty(theZone, "saveFileName", dcsCommon.getMissionName() .. " Data.txt")
if theZone:hasProperty("saveFileName") then
persistence.saveFileName = theZone:getStringFromZoneProperty("saveFileName", dcsCommon.getMissionName() .. " Data.txt")
end
if cfxZones.hasProperty(theZone, "versionID") then
persistence.versionID = cfxZones.getStringFromZoneProperty(theZone, "versionID", "") -- to check for full restart
if theZone:hasProperty("versionID") then
persistence.versionID = theZone:getStringFromZoneProperty("versionID", "") -- to check for full restart
end
persistence.saveInterval = cfxZones.getNumberFromZoneProperty(theZone, "saveInterval", -1) -- default to manual save
persistence.saveInterval = theZone:getNumberFromZoneProperty("saveInterval", -1) -- default to manual save
if persistence.saveInterval > 0 then
persistence.saveTime = persistence.saveInterval * 60 + timer.getTime()
end
if cfxZones.hasProperty(theZone, "cleanRestart?") then
persistence.cleanRestart = cfxZones.getStringFromZoneProperty(theZone, "cleanRestart?", "*<none>")
persistence.lastCleanRestart = cfxZones.getFlagValue(persistence.cleanRestart, theZone)
if theZone:hasProperty("cleanRestart?") then
persistence.cleanRestart = theZone:getStringFromZoneProperty("cleanRestart?", "*<none>")
persistence.lastCleanRestart = theZone:getFlagValue(persistence.cleanRestart)
end
if cfxZones.hasProperty(theZone, "saveMission?") then
persistence.saveMission = cfxZones.getStringFromZoneProperty(theZone, "saveMission?", "*<none>")
persistence.lastSaveMission = cfxZones.getFlagValue(persistence.saveMission, theZone)
if theZone:hasProperty("saveMission?") then
persistence.saveMission = theZone:getStringFromZoneProperty("saveMission?", "*<none>")
persistence.lastSaveMission = theZone:getFlagValue(persistence.saveMission)
end
persistence.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false)
persistence.verbose = theZone.verbose
persistence.saveNotification = cfxZones.getBoolFromZoneProperty(theZone, "saveNotification", true)
persistence.saveNotification = theZone:getBoolFromZoneProperty("saveNotification", true)
if persistence.verbose then
trigger.action.outText("+++persistence: read config", 30)
@ -534,14 +520,13 @@ function persistence.start()
return false
end
-- local mainDir = lfs.writedir() .. persistence.serverDir
local mainDir = persistence.root .. persistence.serverDir
if not dcsCommon.stringEndsWith(mainDir, "\\") then
mainDir = mainDir .. "\\"
end
-- lets see if we can access the server's mission directory and
-- save directory
if persistence.isDir(mainDir) then
if persistence.verbose then
trigger.action.outText("persistence: main dir is <" .. mainDir .. ">", 30)
@ -613,7 +598,6 @@ function persistence.start()
-- and start updating
persistence.update()
return persistence.active
end
@ -627,5 +611,3 @@ if not persistence.start() then
end
-- we do NOT remove the methods so we don't crash
end
-- add zones for saveFlags so authors can easily save flag values

View File

@ -1,91 +1,17 @@
cfxPlayerScore = {}
cfxPlayerScore.version = "3.0.0"
cfxPlayerScore.version = "3.0.1"
cfxPlayerScore.name = "cfxPlayerScore" -- compatibility with flag bangers
cfxPlayerScore.badSound = "Death BRASS.wav"
cfxPlayerScore.scoreSound = "Quest Snare 3.wav"
cfxPlayerScore.announcer = true
cfxPlayerScore.firstSave = true -- to force overwrite
--[[-- VERSION HISTORY
1.0.1 - bug fixes to killDetected
1.0.2 - messaging clean-up, less verbose
1.1.0 - integrated score base system
- accepts configZones
- module validation
- isNamedUnit(theUnit)
- notify if named unit killed
- kill weapon reported
1.2.0 - score table
- announcer attribute
- badSound name
- scoreSound name
1.3.0 - object2score
- static objects also can score
- can now also score members of group by adding group name
- scenery objects are now supported. use the
number that is given under OBJECT ID when
using assign as...
1.3.1 - isStaticObject() to better detect buildings after Match 22 patch
1.3.2 - corrected ground default score
- removed dependency to cfxPlayer
1.4.0 - persistence support
- better unit-->static switch support for generic type kill
1.5.0 - added feats to score
- feats API
- logFeatForPlayer(playerName, theFeat, coa)
1.5.1 - init feats before reading
2.0.0 - sound name for good and bad corrected
- scoreOnly option
- saveScore? input in config
- incremental option in config for save
- preProcessor() added land and birth for players
- addSafeZone()
- landing score possible as feat
- also detect landing and birth events for players
- birth zeroes deferred scores and feats
- delayAfterLanding property
- delayBetweenLandings to filter landings and
space them properly from take offs
- ranking option
- scoreOnly option
- scoreFileName option
- update() loop
- read "scoreSafe" zones
- scoreaccu in playerScore
- landing feat added, enabled with landing > 0 score attribute
- delayed awards after landing
- save score to file
- show score to all
- feat zones
- awardLimit - feats can be limited in number total
- wildcard support for feature
- kill zones - limit kill score awards to inside zones
- feats can be limited to once per player
- persistence: featNum
- persistence: awardedTo
- always schedule
- hasAward logic
- unit2player
- detect player plane death
- ffMod attribute
- pkMod attribute
- pvp feat
- immediate awarding of all negative scores, even if deferred
2.0.1 - corrected access to nowString()
- more robust config reading
2.1.0 - coalition score
- reportCoalition switch
- persist coalition score
- add score to coalition when scoring player
2.1.1 - check ownership of scoreSafe zone upon touch-down
- new scoreSummaryForPlayersOfCoalition()
- new noGrief option in config
- improved guards when checking ownership (nil zone owner)
2.2.0 - score flags for red and blue
3.0.0 - dmlFlags OOP
- redScore#
- blueScore#
- sceneryObject detection improvements
- DCS 2.9 safe
3.0.1 - cleanup
--]]--
@ -109,12 +35,6 @@ cfxPlayerScore.killZones = {} -- when set, kills only count here
cfxPlayerScore.typeScore = {}
cfxPlayerScore.lastPlayerLanding = {} -- timestamp, by player name
cfxPlayerScore.delayBetweenLandings = 30 -- seconds to count as separate landings, also set during take-off to prevent janky t/o to count.
--
-- we subscribe to the kill event. each time a unit
-- is killed, we check if it was killed by a player
-- and if so, that player record is updated and the side
-- whom the player belongs to is informed
--
cfxPlayerScore.aircraft = 50
cfxPlayerScore.helo = 40
cfxPlayerScore.ground = 10
@ -655,7 +575,6 @@ function cfxPlayerScore.linkUnitWithPlayer(theUnit)
local uName = theUnit:getName()
local pName = theUnit:getPlayerName()
cfxPlayerScore.unit2player[uName] = pName
--cfxPlayerScore.player2unit[pName] = uName -- is this needed?
end
function cfxPlayerScore.unlinkUnit(theUnit)
@ -790,7 +709,6 @@ function cfxPlayerScore.checkKillFeat(name, killer, victim, fratricide)
local killFeats = cfxPlayerScore.featsForLocation(name, theLoc, coa,"KILL", killer, victim)
if (not fratricide) and #killFeats > 0 then
-- use the feat description
-- we may want to use closest, currently simply the first
theFeatZone = killFeats[1]
@ -826,7 +744,6 @@ function cfxPlayerScore.killDetected(theEvent)
if wasBuilding then
-- these objects have no coalition; we simply award the score if
-- it exists in look-up table.
--trigger.action.outText("KILL SCENERY", 30)
local staticScore = cfxPlayerScore.object2score(victim)
if staticScore > 0 then
trigger.action.outSoundForCoalition(killSide, cfxPlayerScore.scoreSound)
@ -848,9 +765,8 @@ function cfxPlayerScore.killDetected(theEvent)
--if not victim.getGroup then
if isStO then
-- static objects have no group
local staticName = victim:getName() -- on statics, this returns
-- name as entered in TOP LINE
-- name as entered in TOP LINE
local staticScore = cfxPlayerScore.object2score(victim)
if staticScore > 0 then
@ -871,8 +787,6 @@ function cfxPlayerScore.killDetected(theEvent)
if not fraternicide then
cfxPlayerScore.checkKillFeat(killerName, killer, victim, false)
end
return
end
@ -881,7 +795,7 @@ function cfxPlayerScore.killDetected(theEvent)
trigger.action.outText("+++scr: strange stuff:group, outta here", 30)
return
end
local vicCat = vicGroup:getCategory()
local vicCat = vicGroup:getCategory() -- group cat is DCS 2.9 safe
if not vicCat then
trigger.action.outText("+++scr: strange stuff:cat, outta here", 30)
return
@ -889,25 +803,24 @@ function cfxPlayerScore.killDetected(theEvent)
local unitScore = cfxPlayerScore.unit2score(victim)
-- see which weapon was used. gun kills score 2x
local killMeth = ""
local killWeap = theEvent.weapon
-- local killMeth = "" -- meth is currently not defined
-- local killWeap = theEvent.weapon -- not supported either
if pk then
if pk then -- player kill - add player's name
vicDesc = victim:getPlayerName() .. " in " .. vicDesc
scoreMod = scoreMod * cfxPlayerScore.pkMod
end
-- if fratricide, times ffMod (friedlyFire)
if fraternicide then
scoreMod = scoreMod * cfxPlayerScore.ffMod ---2
if cfxPlayerScore.announcer then
trigger.action.outTextForCoalition(killSide, killerName .. " in " .. killVehicle .. " killed FRIENDLY " .. vicDesc .. killMeth .. "!", 30)
trigger.action.outTextForCoalition(killSide, killerName .. " in " .. killVehicle .. " killed FRIENDLY " .. vicDesc .. "!", 30)
trigger.action.outSoundForCoalition(killSide, cfxPlayerScore.badSound)
end
else
if cfxPlayerScore.announcer then
trigger.action.outText(killerName .. " in " .. killVehicle .." killed " .. vicDesc .. killMeth .."!", 30)
trigger.action.outText(killerName .. " in " .. killVehicle .." killed " .. vicDesc .. "!", 30)
trigger.action.outSoundForCoalition(vicSide, cfxPlayerScore.badSound)
trigger.action.outSoundForCoalition(killSide, cfxPlayerScore.scoreSound)
end
@ -960,7 +873,6 @@ function cfxPlayerScore.handlePlayerLanding(theEvent)
-- we may want to use closest, currently simply the first
theFeatZone = landingFeats[1]
desc = cfxPlayerScore.evalFeatDescription(playerName, theFeatZone, thePlayerUnit) -- nil victim, defaults to player
else
if theEvent.place then
desc = desc .. " successfully (" .. theEvent.place:getName() .. ")"
@ -1068,7 +980,6 @@ function cfxPlayerScore.scheduledAward(args)
return
end
local theScore = cfxPlayerScore.getPlayerScore(playerName)
local playerSide = dcsCommon.playerName2Coalition(playerName)
if playerSide < 1 then
@ -1214,59 +1125,58 @@ function cfxPlayerScore.handlePlayerEvent(theEvent)
end
function cfxPlayerScore.readConfigZone(theZone)
cfxPlayerScore.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false)
cfxPlayerScore.verbose = theZone.verbose
-- default scores
cfxPlayerScore.aircraft = cfxZones.getNumberFromZoneProperty(theZone, "aircraft", 50)
cfxPlayerScore.helo = cfxZones.getNumberFromZoneProperty(theZone, "helo", 40)
cfxPlayerScore.ground = cfxZones.getNumberFromZoneProperty(theZone, "ground", 10)
cfxPlayerScore.ship = cfxZones.getNumberFromZoneProperty(theZone, "ship", 80)
cfxPlayerScore.train = cfxZones.getNumberFromZoneProperty(theZone, "train", 5)
cfxPlayerScore.landing = cfxZones.getNumberFromZoneProperty(theZone, "landing", 0) -- if > 0 then feat
cfxPlayerScore.aircraft = theZone:getNumberFromZoneProperty("aircraft", 50)
cfxPlayerScore.helo = theZone:getNumberFromZoneProperty("helo", 40)
cfxPlayerScore.ground = theZone:getNumberFromZoneProperty("ground", 10)
cfxPlayerScore.ship = theZone:getNumberFromZoneProperty("ship", 80)
cfxPlayerScore.train = theZone:getNumberFromZoneProperty( "train", 5)
cfxPlayerScore.landing = theZone:getNumberFromZoneProperty("landing", 0) -- if > 0 then feat
cfxPlayerScore.pkMod = cfxZones.getNumberFromZoneProperty(theZone, "pkMod", 1) -- factor for killing a player
cfxPlayerScore.ffMod = cfxZones.getNumberFromZoneProperty(theZone, "ffMod", -2) -- factor for friendly fire
cfxPlayerScore.planeLoss = cfxZones.getNumberFromZoneProperty(theZone, "planeLoss", -10) -- points added when player's plane crashes
cfxPlayerScore.pkMod = theZone:getNumberFromZoneProperty( "pkMod", 1) -- factor for killing a player
cfxPlayerScore.ffMod = theZone:getNumberFromZoneProperty( "ffMod", -2) -- factor for friendly fire
cfxPlayerScore.planeLoss = theZone:getNumberFromZoneProperty("planeLoss", -10) -- points added when player's plane crashes
cfxPlayerScore.announcer = cfxZones.getBoolFromZoneProperty(theZone, "announcer", true)
cfxPlayerScore.announcer = theZone:getBoolFromZoneProperty("announcer", true)
if cfxZones.hasProperty(theZone, "badSound") then
cfxPlayerScore.badSound = cfxZones.getStringFromZoneProperty(theZone, "badSound", "<nosound>")
if theZone:hasProperty("badSound") then
cfxPlayerScore.badSound = theZone:getStringFromZoneProperty("badSound", "<nosound>")
end
if cfxZones.hasProperty(theZone, "scoreSound") then
cfxPlayerScore.scoreSound = cfxZones.getStringFromZoneProperty(theZone, "scoreSound", "<nosound>")
if theZone:hasProperty("scoreSound") then
cfxPlayerScore.scoreSound = theZone:getStringFromZoneProperty("scoreSound", "<nosound>")
end
-- triggering saving scores
if cfxZones.hasProperty(theZone, "saveScore?") then
cfxPlayerScore.saveScore = cfxZones.getStringFromZoneProperty(theZone, "saveScore?", "none")
if theZone:hasProperty("saveScore?") then
cfxPlayerScore.saveScore = theZone:getStringFromZoneProperty("saveScore?", "none")
cfxPlayerScore.lastSaveScore = trigger.misc.getUserFlag(cfxPlayerScore.saveScore)
cfxPlayerScore.incremental = cfxZones.getBoolFromZoneProperty(theZone, "incremental", false) -- incremental saves
cfxPlayerScore.incremental = theZone:getBoolFromZoneProperty("incremental", false) -- incremental saves
end
-- triggering show all scores
if cfxZones.hasProperty(theZone, "showScore?") then
cfxPlayerScore.showScore = cfxZones.getStringFromZoneProperty(theZone, "showScore?", "none")
if theZone:hasProperty("showScore?") then
cfxPlayerScore.showScore = theZone:getStringFromZoneProperty("showScore?", "none")
cfxPlayerScore.lastShowScore = trigger.misc.getUserFlag(cfxPlayerScore.showScore)
end
cfxPlayerScore.rankPlayers = cfxZones.getBoolFromZoneProperty(theZone, "rankPlayers", false)
cfxPlayerScore.rankPlayers = theZone:getBoolFromZoneProperty("rankPlayers", false)
cfxPlayerScore.scoreOnly = cfxZones.getBoolFromZoneProperty(theZone, "scoreOnly", true)
cfxPlayerScore.scoreOnly = theZone:getBoolFromZoneProperty("scoreOnly", true)
cfxPlayerScore.deferred = cfxZones.getBoolFromZoneProperty(theZone, "deferred", false)
cfxPlayerScore.deferred = theZone:getBoolFromZoneProperty("deferred", false)
cfxPlayerScore.delayAfterLanding = cfxZones.getNumberFromZoneProperty(theZone, "delayAfterLanding", 10)
cfxPlayerScore.delayAfterLanding = theZone:getNumberFromZoneProperty("delayAfterLanding", 10)
cfxPlayerScore.scoreFileName = cfxZones.getStringFromZoneProperty(theZone, "scoreFileName", "Player Scores")
cfxPlayerScore.scoreFileName = theZone:getStringFromZoneProperty("scoreFileName", "Player Scores")
cfxPlayerScore.reportScore = cfxZones.getBoolFromZoneProperty(theZone, "reportScore", true)
cfxPlayerScore.reportScore = theZone:getBoolFromZoneProperty("reportScore", true)
cfxPlayerScore.reportFeats = cfxZones.getBoolFromZoneProperty(theZone, "reportFeats", true)
cfxPlayerScore.reportFeats = theZone:getBoolFromZoneProperty("reportFeats", true)
cfxPlayerScore.reportCoalition = cfxZones.getBoolFromZoneProperty(
theZone, "reportCoalition", false) -- also show coalition score
cfxPlayerScore.reportCoalition = theZone:getBoolFromZoneProperty("reportCoalition", false) -- also show coalition score
cfxPlayerScore.noGrief = cfxZones.getBoolFromZoneProperty(theZone, "noGrief", true) -- noGrief = only add positive score
cfxPlayerScore.noGrief = theZone:getBoolFromZoneProperty( "noGrief", true) -- noGrief = only add positive score
if theZone:hasProperty("redScore#") then
cfxPlayerScore.redScoreOut = theZone:getStringFromZoneProperty("redScore#")

View File

@ -10,6 +10,7 @@ cfxPlayerScoreUI.verbose = false
- 2.1.0 - soundfile cleanup
- score summary for side
- allowAll
- 2.1.1 - minor cleanup
--]]--
cfxPlayerScoreUI.requiredLibs = {
@ -116,7 +117,7 @@ function cfxPlayerScoreUI.start()
world.addEventHandler(cfxPlayerScoreUI)
-- process all existing players (late start)
dcsCommon.iteratePlayers(cfxPlayerScore.processPlayerUnit)
trigger.action.outText("cf/x cfxPlayerScoreUI v" .. cfxPlayerScoreUI.version .. " started", 30)
trigger.action.outText("cf/x PlayerScoreUI v" .. cfxPlayerScoreUI.version .. " started", 30)
return true
end

View File

@ -1,40 +1,39 @@
playerZone = {}
playerZone.version = "1.0.0"
playerZone.version = "2.0.0"
playerZone.requiredLibs = {
"dcsCommon", -- always
"cfxZones", -- Zones, of course
"dcsCommon",
"cfxZones",
}
playerZone.playerZones = {}
--[[--
Version History
1.0.0 - Initial version
1.0.1 - pNum --> pNum#
2.0.0 - dmlZones
red#, blue#, total#
--]]--
function playerZone.createPlayerZone(theZone)
-- start val - a range
theZone.pzCoalition = cfxZones.getCoalitionFromZoneProperty(theZone, "playerZone", 0)
theZone.pzCoalition = theZone:getCoalitionFromZoneProperty( "playerZone", 0)
-- Method for outputs
theZone.pzMethod = cfxZones.getStringFromZoneProperty(theZone, "method", "inc")
if cfxZones.hasProperty(theZone, "pzMethod") then
theZone.pzMethod = cfxZones.getStringFromZoneProperty(theZone, "pwMethod", "inc")
theZone.pzMethod = theZone:getStringFromZoneProperty("method", "inc")
if theZone:hasProperty("pzMethod") then
theZone.pzMethod = theZone:getStringFromZoneProperty("pwMethod", "inc")
end
if cfxZones.hasProperty(theZone, "pNum#") then
theZone.pNum = cfxZones.getStringFromZoneProperty(theZone, "pNum#", "none")
elseif cfxZones.hasProperty(theZone, "pNum") then
theZone.pNum = cfxZones.getStringFromZoneProperty(theZone, "pNum", "none")
if theZone:hasProperty("pNum#") then
theZone.pNum = theZone:getStringFromZoneProperty("pNum#", "none")
end
if cfxZones.hasProperty(theZone, "added!") then
theZone.pAdd = cfxZones.getStringFromZoneProperty(theZone, "added!", "none")
if theZone:hasProperty("added!") then
theZone.pAdd = theZone:getStringFromZoneProperty("added!", "none")
end
if cfxZones.hasProperty(theZone, "gone!") then
theZone.pRemove = cfxZones.getStringFromZoneProperty(theZone, "gone!", "none")
if theZone:hasProperty("gone!") then
theZone.pRemove = theZone:getStringFromZoneProperty("gone!", "none")
end
theZone.playersInZone = {} -- indexed by unit name
@ -49,7 +48,7 @@ function playerZone.collectPlayersForZone(theZone)
local allPlayers = coalition.getPlayers(f)
for idy, theUnit in pairs (allPlayers) do
local loc = theUnit:getPoint()
if cfxZones.pointInZone(loc, theZone) then
if theZone:pointInZone(loc) then
zonePlayers[theUnit:getName()] = theUnit
end
end
@ -86,21 +85,21 @@ function playerZone.processZone(theZone)
-- flag handling and banging
if theZone.pNum then
cfxZones.setFlagValueMult(theZone.pNum, newCount, theZone)
theZone:setFlagValue(theZone.pNum, newCount)
end
if theZone.pAdd and hasNew then
if theZone.verbose or playerZone.verbose then
trigger.action.outText("+++pZone: banging <" .. theZone.name .. ">'s 'added!' flags <" .. theZone.pAdd .. ">", 30)
end
cfxZones.pollFlag(theZone.pAdd, theZone.pzMethod, theZone)
theZone:pollFlag(theZone.pAdd, theZone.pzMethod)
end
if theZone.pRemove and hasGone then
if theZone.verbose or playerZone.verbose then
trigger.action.outText("+++pZone: banging <" .. theZone.name .. ">'s 'gone' flags <" .. theZone.pRemove .. ">", 30)
end
cfxZones.pollFlag(theZone.pAdd, theZone.pzMethod, theZone)
theZone:pollFlag(theZone.pAdd, theZone.pzMethod)
end
end
@ -113,6 +112,25 @@ function playerZone.update()
-- iterate all zones and check them
for idx, theZone in pairs(playerZone.playerZones) do
local neutrals = coalition.getPlayers(0)
local neutralNum = #neutrals
local reds = coalition.getPlayers(1)
local redNum = #reds
local blues = coalition.getPlayers(2)
local blueNum = #blues
local totalNum = neutralNum + redNum + blueNum
if playerZone.neutralNum then
cfxZones.setFlagValue(playerZone.neutralNum, neutralNum, playerZone)
end
if playerZone.redNum then
cfxZones.setFlagValue(playerZone.redNum, redNum, playerZone)
end
if playerZone.blueNum then
cfxZones.setFlagValue(playerZone.blueNum, blueNum, playerZone)
end
if playerZone.totalNum then
cfxZones.setFlagValue(playerZone.totalNum, totalNum, playerZone)
end
playerZone.processZone(theZone)
end
end
@ -121,7 +139,20 @@ end
-- Read Config Zone
--
function playerZone.readConfigZone(theZone)
playerZone.name = "playerZoneConfig" -- cfxZones compat
-- currently nothing to do
if theZone:hasProperty("red#") then
playerZone.redNum = theZone:getStringFromZoneProperty("red#", "none")
end
if theZone:hasProperty("blue#") then
playerZone.blueNum = theZone:getStringFromZoneProperty("blue#", "none")
end
if theZone:hasProperty("neutral#") then
playerZone.neutralNum = theZone:getStringFromZoneProperty("neutral#", "none")
end
if theZone:hasProperty("total#") then
playerZone.totalNum = theZone:getStringFromZoneProperty("total#", "none")
end
end
--

View File

@ -11,34 +11,6 @@ pulseFlags.requiredLibs = {
Copyright 2022 by Christian Franz and cf/x
Version History
- 1.0.0 Initial version
- 1.0.1 pause behavior debugged
- 1.0.2 zero pulse optional initial pulse suppress
- 1.0.3 pollFlag switched to cfxZones
uses randomDelayFromPositiveRange
flag! now is string
WARNING: still needs full alphaNum flag upgrade
- 1.1.0 Full DML flag integration
removed zone!
made pulse and pulse! the out flag carrier
done!
pulsesDone! synonym
pausePulse? synonym
pulseMethod synonym
startPulse? synonym
pulseStopped synonym
- 1.2.0 DML Watchflag integration
corrected bug in loading last pulse value for paused
- 1.2.1 pulseInterval synonym for time
pulses now supports range
zone-local verbosity
- 1.2.2 outputMethod synonym
- 1.2.3 deprecated paused/pulsePaused
returned onStart, defaulting to true
- 1.3.0 persistence
- 1.3.1 typos corrected
- 1.3.2 removed last pulse's timeID upon entry in doPulse
- 1.3.3 removed 'pulsing' when pausing, so we can restart
- 2.0.0 dmlZones / OOP
using method on all outputs
- 2.0.1 activateZoneFlag now works correctly
@ -165,7 +137,7 @@ function pulseFlags.doPulse(args)
-- first, we only do an initial pulse if zeroPulse is set
if theZone.hasPulsed or theZone.zeroPulse then
if pulseFlags.verbose or theZone.verbose then
trigger.action.outText("+++pulF: will bang " .. theZone.pulseFlag, 30);
trigger.action.outText("+++pulF: will bang " .. theZone.pulseFlag .. " for <" .. theZone.name .. ">", 30);
end
theZone:pollFlag(theZone.pulseFlag, theZone.pulseMethod)

View File

@ -1,5 +1,5 @@
radioMenu = {}
radioMenu.version = "2.1.1"
radioMenu.version = "2.2.0"
radioMenu.verbose = false
radioMenu.ups = 1
radioMenu.requiredLibs = {
@ -10,21 +10,6 @@ radioMenu.menus = {}
--[[--
Version History
1.0.0 - Initial version
1.0.1 - spelling corrections
1.1.0 - removeMenu
addMenu
menuVisible
2.0.0 - redesign: handles multiple receivers
optional MX module
group option
type option
multiple group names
multiple types
gereric helo type
generic plane type
type works with coalition
2.0.1 - corrections to installMenu(), as suggested by GumidekCZ
2.1.0 - valA/valB/valC/valD attributes
OOP cfxZones
corrected CD setting for "D"
@ -32,6 +17,7 @@ radioMenu.menus = {}
valA-D now define full method, not just values
full wildcard support for ack and cooldown
2.1.1 - outMessage now works correctly
2.2.0 - clean-up
--]]--
function radioMenu.addRadioMenu(theZone)
@ -63,8 +49,7 @@ function radioMenu.filterPlayerIDForType(theZone)
end
-- now iterate all types, and include any player that matches
-- note that a player may match twice, so we use a dict instead of an
-- array. Since we later iterate ID by idx, that's not an issue
-- note that players may match twice, so we use a dict
for idx, aType in pairs(allTypes) do
local theType = dcsCommon.trim(aType)
@ -145,7 +130,6 @@ function radioMenu.filterPlayerIDForGroup(theZone)
end
function radioMenu.installMenu(theZone)
-- local theGroup = 0 -- was: nil
local gID = nil
if theZone.menuGroup then
if not cfxMX then
@ -570,6 +554,5 @@ if not radioMenu.start() then
end
--[[--
callbacks for the menus
check CD/standby code for multiple groups
--]]--

View File

@ -1,25 +1,12 @@
cfxSmokeZone = {}
cfxSmokeZone.version = "1.2.0"
cfxSmokeZone.version = "2.0.0"
cfxSmokeZone.requiredLibs = {
"dcsCommon", -- always
"cfxZones", -- Zones, of course
}
--[[--
Version History
1.0.0 - initial version
1.0.1 - added removeSmokeZone
1.0.2 - added altitude
1.0.3 - added paused attribute
- added f? attribute --> onFlag
- broke out startSmoke
1.0.4 - startSmoke? synonym
- alphanum DML flag upgrade
- random color support
1.1.0 - Watchflag upgrade
1.1.1 - stopSmoke? input
1.1.2 - 'agl', 'alt' synonymous for altitude to keep in line with fireFX
1.1.3 - corrected smokeTriggerMethod in zone definition
1.2.0 - first OOP guinea pig.
2.0.0 - clean up
--]]--
cfxSmokeZone.smokeZones = {}
@ -48,7 +35,6 @@ function cfxSmokeZone.processSmokeZone(aZone)
-- paused
aZone.paused = aZone:getBoolFromZoneProperty("paused", false)
-- f? query flags
if aZone:hasProperty("f?") then
aZone.onFlag = aZone:getStringFromZoneProperty("f?", "*<none>")
elseif aZone:hasProperty("startSmoke?") then
@ -64,8 +50,6 @@ function cfxSmokeZone.processSmokeZone(aZone)
aZone.smkLastStopFlag = aZone:getFlagValue(aZone.smkStopFlag)
end
-- watchflags:
-- triggerMethod
aZone.smokeTriggerMethod = aZone:getStringFromZoneProperty( "triggerMethod", "change")
if aZone:hasProperty("smokeTriggerMethod") then
@ -165,14 +149,10 @@ function cfxSmokeZone.start()
end
-- collect all zones with 'smoke' attribute
-- collect all spawn zones
local attrZones = cfxZones.getZonesWithAttributeNamed("smoke")
-- now create a smoker for all, add them to updater,
-- smoke all that aren't paused
for k, aZone in pairs(attrZones) do
cfxSmokeZone.processSmokeZone(aZone) -- process attribute and add to zone
cfxSmokeZone.addSmokeZone(aZone) -- remember it so we can smoke it
for k, aZone in pairs(attrZones) do
cfxSmokeZone.processSmokeZone(aZone)
cfxSmokeZone.addSmokeZone(aZone)
end
-- start update loop

View File

@ -22,52 +22,6 @@ cfxSpawnZones.spawnedGroups = {}
--
--[[--
-- version history
1.3.0
- maxSpawn
- orders
- range
1.3.1 - spawnWithSpawner correct translation of country to coalition
- createSpawner - corrected reading from properties
1.3.2 - createSpawner - correct reading 'owner' from properties, now
directly reads coalition
1.4.0 - checks modules
- orders 'train' or 'training' - will make the
ground troops be issued HOLD WEAPS and
not added to any queue. 'Training' troops
are target dummies.
- optional heading attribute
- typeMult: repeate type this many time (can produce army in one call)
1.4.1 - 'requestable' attribute. will automatically set zone to
- paused, so troops can be produced on call
- getRequestableSpawnersInRange
1.4.2 - target attribute. used for
- orders: attackZone
- spawner internally copies name from cfxZone used for spawning (convenience only)
1.4.3 - can subscribe to callbacks. currently called when spawnForSpawner is invoked, reason is "spawned"
- masterOwner to link ownership to other zone
1.4.4 - autoRemove flag to instantly start CD and respawn
1.4.5 - verify that maxSpawns ~= 0 on initial spawn on start-up
1.4.6 - getSpawnerForZoneNamed(aName)
- nil-trapping orders before testing for 'training'
1.4.7 - defaulting orders to 'guard'
- also accept 'dummy' and 'dummies' as substitute for training
1.4.8 - spawnWithSpawner uses getPoint to support linked spawn zones
- update spawn count on initial spawn
1.5.0 - f? support to trigger spawn
- spawnWithSpawner made string compatible
1.5.1 - relaxed baseName and default to dcsCommon.uuid()
- verbose
1.5.2 - activate?, pause? flag
1.5.3 - spawn?, spawnUnits? flags
1.6.0 - trackwith interface for group tracker
1.7.0 - persistence support
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
1.7.4 - wait-attackZone fixes
1.7.5 - improved verbosity on spawning
- getRequestableSpawnersInRange() ignores height for distance
2.0.0 - dmlZones
- moved "types" to spawner
- baseName defaults to zone name, as it is safe for naming

View File

@ -324,6 +324,9 @@ function stopGap.update()
busy = true
-- count up for auto-release after n seconds
trigger.action.setUserFlag(theGroup.sgName, sgState + 1)
if stopGap.verbose then
trigger.action.outText("+++StopG: [cooldown] cooldown for group <" .. name .. ">, val now is <" .. sgState .. ">.", 30)
end
end
if busy then
@ -331,6 +334,9 @@ function stopGap.update()
else
local theStaticGroup = stopGap.createStandInsForMXGroup(theGroup)
stopGap.standInGroups[name] = theStaticGroup
if stopGap.verbose then
trigger.action.outText("+++StopG: [server command] placing static stand-in for group <" .. name .. ">.", 30)
end
end
else
-- plane is currently static and visible

View File

@ -52,7 +52,7 @@ stopGap.requiredLibs = {
--]]--
stopGap.standInGroups = {}
stopGap.standInGroups = {} -- idx by name, if set has a static
stopGap.myGroups = {} -- for fast look-up of mx orig data
stopGap.stopGapZones = {} -- DML only

View File

@ -12,24 +12,27 @@ debugger.log = ""
--[[--
Version History
1.0.0 - Initial version
1.0.1 - made ups available to config zone
- changed 'on' to 'active' in config zone
- merged debugger and debugDemon
- QoL check for 'debug' attribute (no '?')
1.1.0 - logging
- trigger.action --> debugger for outText
- persistence of logs
- save <name>
1.1.1 - warning when trying to set a flag to a non-int
1.1.2 - remove command
2.0.0 - dmlZones OOP
- eventmon command
- all, off, event #
- eventmon all, off, event #
- standard events
- adding events
- adding events via #
- events? attribute from any zone
- eventmon last command
- q - query MSE Lua variables
- w - write/overwrite MSE Lua variables
- a - analyse Lua tables / variables
- smoke
- spawn system with predefines
- spawn coalition
- spawn number
- spawn heading
- spawn types
- spawn aircraft: add waypoints
- spawn "?"
- debuggerSpawnTypes zone
- reading debuggerSpawnTypes
- removed some silly bugs / inconsistencies
--]]--
debugger.requiredLibs = {
@ -45,6 +48,7 @@ debugger.debugUnits = {}
debugger.debugGroups = {}
debugger.debugObjects = {}
debugger.showEvents = {}
debugger.lastEvent = nil
debugDemon.eventList = {
["0"] = "S_EVENT_INVALID = 0",
@ -106,6 +110,26 @@ debugDemon.eventList = {
["56"] = "S_EVENT_POSTPONED_LAND = 56",
["57"] = "S_EVENT_MAX = 57",
}
debugger.spawnTypes = {
["inf"] = "Soldier M4",
["ifv"] = "BTR-80",
["tank"] = "T-90",
["ship"] = "PERRY",
["helo"] = "AH-1W",
["jet"] = "MiG-21Bis",
["awacs"] = "A-50",
["ww2"] = "SpitfireLFMkIX",
["bomber"] = "B-52H",
["cargo"] = "ammo_cargo",
["sam"] = "Roland ADS",
["aaa"] = "ZSU-23-4 Shilka",
["arty"] = "M-109",
["truck"] = "KAMAZ Truck",
["drone"] = "MQ-9 Reaper",
["manpad"] = "Soldier stinger",
["obj"] = "house2arm"
}
--
-- Logging & saving
--
@ -235,7 +259,6 @@ function debugger.createEventMonWithZone(theZone)
debugger.outText("*** monitoring events defined in <" .. theZone.name .. ">:", 30)
end
for idx, aFlag in pairs(flagArray) do
local evt = tonumber(aFlag)
if evt and (debugger.verbose or theZone.verbose) then
if evt < 0 then evt = 0 end
@ -288,7 +311,6 @@ function debugger.isObserving(flagName)
end
end
end
return observers
end
@ -476,43 +498,62 @@ end
function debugger.readConfigZone()
local theZone = cfxZones.getZoneByName("debuggerConfig")
if not theZone then
if debugger.verbose then
debugger.outText("+++debug: NO config zone!", 30)
end
theZone = cfxZones.createSimpleZone("debuggerConfig")
end
debugger.configZone = theZone
debugger.active = cfxZones.getBoolFromZoneProperty(theZone, "active", true)
debugger.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false)
debugger.active = theZone:getBoolFromZoneProperty("active", true)
debugger.verbose = theZone.verbose
if cfxZones.hasProperty(theZone, "on?") then
debugger.onFlag = cfxZones.getStringFromZoneProperty(theZone, "on?", "<none>")
if theZone:hasProperty("on?") then
debugger.onFlag = theZone:getStringFromZoneProperty("on?", "<none>")
debugger.lastOn = cfxZones.getFlagValue(debugger.onFlag, theZone)
end
if cfxZones.hasProperty(theZone, "off?") then
debugger.offFlag = cfxZones.getStringFromZoneProperty(theZone, "off?", "<none>")
if theZone:hasProperty("off?") then
debugger.offFlag = theZone:getStringFromZoneProperty("off?", "<none>")
debugger.lastOff = cfxZones.getFlagValue(debugger.offFlag, theZone)
end
if cfxZones.hasProperty(theZone, "reset?") then
debugger.resetFlag = cfxZones.getStringFromZoneProperty(theZone, "reset?", "<none>")
if theZone:hasProperty("reset?") then
debugger.resetFlag = theZone:getStringFromZoneProperty("reset?", "<none>")
debugger.lastReset = cfxZones.getFlagValue(debugger.resetFlag, theZone)
end
if cfxZones.hasProperty(theZone, "state?") then
debugger.stateFlag = cfxZones.getStringFromZoneProperty(theZone, "state?", "<none>")
if theZone:hasProperty("state?") then
debugger.stateFlag = theZone:getStringFromZoneProperty("state?", "<none>")
debugger.lastState = cfxZones.getFlagValue(debugger.stateFlag, theZone)
end
debugger.ups = cfxZones.getNumberFromZoneProperty(theZone, "ups", 4)
debugger.ups = theZone:getNumberFromZoneProperty("ups", 4)
end
if debugger.verbose then
debugger.outText("+++debug: read config", 30)
function debugger.readSpawnTypeZone()
local theZone = cfxZones.getZoneByName("debuggerSpawnTypes")
if not theZone then
theZone = cfxZones.createSimpleZone("debuggerSpawnTypes")
end
local allAttribuites = theZone:getAllZoneProperties()
for attrName, aValue in pairs(allAttribuites) do
local theLow = string.lower(attrName)
local before = debugger.spawnTypes[theLow]
if before then
debugger.spawnTypes[theLow] = aValue
if theZone.verbose or debugger.verbose then
trigger.action.outText("+++debug: changed generic '" .. theLow .. "' from <" .. before .. "> to <" .. aValue .. ">", 30)
end
else
if theZone.verbose or debugger.verbose then
if theLow == "verbose" then -- filtered
else
trigger.action.outText("+++debug: generic '" .. theLow .. "' unknown, not replaced.", 30)
end
end
end
end
end
function debugger.start()
-- lib check
if not dcsCommon.libCheck then
@ -526,6 +567,9 @@ function debugger.start()
-- read config
debugger.readConfigZone()
-- read spawn types
debugger.readSpawnTypeZone()
-- process debugger Zones
-- old style
local attrZones = cfxZones.getZonesWithAttributeNamed("debug?")
@ -536,7 +580,7 @@ function debugger.start()
local attrZones = cfxZones.getZonesWithAttributeNamed("debug")
for k, aZone in pairs(attrZones) do
debugger.outText("***Warning: Zone <" .. aZone.name .. "> has a 'debug' flag. Are you perhaps missing a '?'", 30)
debugger.outText("***Warning: Zone <" .. aZone.name .. "> has a 'debug' attribute. Are you perhaps missing a '?'", 30)
end
local attrZones = cfxZones.getZonesWithAttributeNamed("events?")
@ -546,7 +590,7 @@ function debugger.start()
local attrZones = cfxZones.getZonesWithAttributeNamed("events")
for k, aZone in pairs(attrZones) do
debugger.outText("***Warning: Zone <" .. aZone.name .. "> has a 'debug' flag. Are you perhaps missing a '?'", 30)
debugger.outText("***Warning: Zone <" .. aZone.name .. "> has an 'events' attribute. Are you perhaps missing a '?'", 30)
end
-- events
@ -683,7 +727,7 @@ function debugDemon.getArgs(theCommands)
end
--
-- stage demon's main command interpreter.
-- debug demon's main command interpreter.
-- magic lies in using the keywords as keys into a
-- function table that holds all processing functions
-- I wish we had that back in the Oberon days.
@ -743,7 +787,7 @@ debugger.outText("*** debugger: commands are:" ..
"\n " .. debugDemon.markOfDemon .. "o <flagname> [with <observername>] -- observe a flag for change" ..
"\n " .. debugDemon.markOfDemon .. "forget <flagname> [with <observername>] -- stop observing a flag" ..
"\n " .. debugDemon.markOfDemon .. "new <observername> [[for] <condition>] -- create observer for flags" ..
"\n " .. debugDemon.markOfDemon .. "update <observername> [[to] <condition>] -- change observer's condition" ..
"\n " .. debugDemon.markOfDemon .. "update <observername> [to] <condition> -- change observer's condition" ..
"\n " .. debugDemon.markOfDemon .. "drop <observername> -- remove observer from debugger" ..
"\n " .. debugDemon.markOfDemon .. "list [<match>] -- list observers [name contains <match>]" ..
"\n " .. debugDemon.markOfDemon .. "who <flagname> -- all who observe <flagname>" ..
@ -752,10 +796,16 @@ debugger.outText("*** debugger: commands are:" ..
"\n\n " .. debugDemon.markOfDemon .. "snap [<observername>] -- create new snapshot of flags" ..
"\n " .. debugDemon.markOfDemon .. "compare -- compare snapshot flag values with current" ..
"\n " .. debugDemon.markOfDemon .. "note <your note> -- add <your note> to the text log" ..
"\n\n " .. debugDemon.markOfDemon .. "remove <group/unit/object name> -- remove named item from mission" ..
"\n\n " .. debugDemon.markOfDemon .. "spawn [<number>] [<coalition>] <type> [heading=<number>] | [?] -- spawn" ..
"\n units/aircraft/objects (? for help)" ..
"\n " .. debugDemon.markOfDemon .. "remove <group/unit/object name> -- remove named item from mission" ..
"\n " .. debugDemon.markOfDemon .. "smoke <color> -- place colored smoke on the ground" ..
"\n " .. debugDemon.markOfDemon .. "boom <number> -- place explosion of strenght <number> on the ground" ..
"\n\n " .. debugDemon.markOfDemon .. "eventmon [all | off | <number> | ?] -- show events for all | none | event <number> | list" ..
"\n " .. debugDemon.markOfDemon .. "eventmon last -- analyse last reported event" ..
"\n\n " .. debugDemon.markOfDemon .. "q <Lua Var> -- Query value of Lua variable <Lua Var>" ..
"\n " .. debugDemon.markOfDemon .. "a <Lua Var> -- Analyse structure of Lua variable <Lua Var>" ..
"\n " .. debugDemon.markOfDemon .. "w <Lua Var> [=] <Lua Value> -- Write <Lua Value> to variable <Lua Var>" ..
"\n\n " .. debugDemon.markOfDemon .. "start -- starts debugger" ..
"\n " .. debugDemon.markOfDemon .. "stop -- stop debugger" ..
@ -815,7 +865,7 @@ function debugDemon.processNewCommand(args, event)
end
function debugDemon.processUpdateCommand(args, event)
-- syntax update <observername> [[to] <condition>]
-- syntax update <observername> [to] <condition>
local observerName = args[1]
if not observerName then
debugger.outText("*** update: missing observer name.", 30)
@ -1271,6 +1321,7 @@ end
function debugDemon.doEventMon(theEvent)
if not theEvent then return end
if not debugger.active then return end
local ID = theEvent.id
if debugger.showEvents[ID] then
-- we show this event
@ -1287,11 +1338,47 @@ function debugDemon.doEventMon(theEvent)
end
end
debugger.outText(m, 30)
-- save it to lastevent so we can analyse
debugger.lastEvent = theEvent
end
end
debugDemon.m = ""
-- dumpVar2m, invoke externally dumpVar2m(varname, var)
function debugDemon.dumpVar2m(key, value, prefix, inrecursion)
-- based on common's dumpVar, appends to var "m"
if not inrecursion then
-- start, init m
debugDemon.m = "analysis of <" .. key .. ">\n==="
end
if not value then value = "nil" end
if not prefix then prefix = "" end
prefix = " " .. prefix
if type(value) == "table" then
debugDemon.m = debugDemon.m .. "\n" .. prefix .. key .. ": [ "
-- iterate through all kvp
for k,v in pairs (value) do
debugDemon.dumpVar2m(k, v, prefix, true)
end
debugDemon.m = debugDemon.m .. "\n" .. prefix .. " ] - end " .. key
elseif type(value) == "boolean" then
local b = "false"
if value then b = "true" end
debugDemon.m = debugDemon.m .. "\n" .. prefix .. key .. ": " .. b
else -- simple var, show contents, ends recursion
debugDemon.m = debugDemon.m .. "\n" .. prefix .. key .. ": " .. value
end
if not inrecursion then
-- output a marker to find in the log / screen
debugDemon.m = debugDemon.m .. "\n" .. "=== analysis end\n"
end
end
function debugDemon.processEventMonCommand(args, event)
-- turn event monito on/off
-- turn event monitor all/off/?/last
-- syntax: -eventmon on|off
local aParam = dcsCommon.trim(event.remainder)
if not aParam or aParam:len() < 1 then
@ -1300,7 +1387,6 @@ function debugDemon.processEventMonCommand(args, event)
aParam = string.upper(aParam)
evtNum = tonumber(aParam)
if aParam == "ON" or aParam == "ALL" then
-- debugger.eventmon = true
debugger.outText("*** eventmon: turned ON, showing ALL events", 30)
local events = {}
for idx,evt in pairs(debugDemon.eventList) do
@ -1322,6 +1408,13 @@ function debugDemon.processEventMonCommand(args, event)
m = m .. "\n" .. evt
end
debugger.outText(m .. "\n*** end of list", 30)
elseif aParam == "LAST" then
if debugger.lastEvent then
debugDemon.dumpVar2m("event", debugger.lastEvent)
debugger.outText(debugDemon.m, 39)
else
debugger.outText("*** eventmon: no event on record", 39)
end
else
debugger.outText("*** eventmon: unknown parameter <" .. event.remainder .. ">", 30)
end
@ -1335,9 +1428,7 @@ end
function debugDemon.processQueryCommand(args, event)
-- syntax -q <name> with name a (qualified) Lua table reference
local theName = args[1]
-- local p = args [2]
-- trigger.action.outText("args1 = " .. theName, 30)
-- if args[2] then trigger.action.outText("param = " .. args[2], 30) end
if not theName then
debugger.outText("*** q: missing Lua table/element name.", 30)
return false -- allows correction
@ -1369,6 +1460,33 @@ function debugDemon.processQueryCommand(args, event)
return true
end
function debugDemon.processAnalyzeCommand(args, event)
-- syntax -a <name> with name a (qualified) Lua table reference
local theName = args[1]
if not theName then
debugger.outText("*** a: missing Lua table/element name.", 30)
return false -- allows correction
end
theName = dcsCommon.stringRemainsStartingWith(event.remainder, theName)
-- put this into a string, and execute it
local exec = "return " .. theName
local f = loadstring(exec)
local res
if pcall(f) then
res = f()
debugDemon.dumpVar2m(theName, res)
res = debugDemon.m
else
res = "[Lua error]"
end
debugger.outText("[" .. dcsCommon.nowString() .. "] <" .. theName .. "> = ".. res, 30)
return true
end
function debugDemon.processWriteCommand(args, event)
-- syntax -w <name> <value> with name a (qualified) Lua table reference and value a Lua value (including strings, with quotes of course). {} means an empty set etc. you CAN call into DCS MSE with this, and create a lot of havoc.
-- also, allow "=" semantic, -w p = {x=1, y=2}
@ -1402,6 +1520,324 @@ function debugDemon.processWriteCommand(args, event)
return true
end
--
-- smoke & boom
--
function debugDemon.processSmokeCommand(args, event)
-- syntax -color
local color = 0 -- green default
local colorCom = args[1]
if colorCom then
colorCom = colorCom:lower()
if colorCom == "red" or colorCom == "1" then color = 1
elseif colorCom == "white" or colorCom == "2" then color = 2
elseif colorCom == "orange" or colorCom == "3" then color = 3
elseif colorCom == "blue" or colorCom == "4" then color = 4
elseif colorCom == "green" or colorCom == "0" then color = 0
else
debugger.outText("*** smoke: unknown color <" .. colorCom .. ">, using green.", 30)
end
local pos = event.pos
local h = land.getHeight({x = pos.x, y = pos.z}) + 1
local p = { x = event.pos.x, y = h, z = event.pos.z}
trigger.action.smoke(p, color)
debugger.outText("*** smoke: placed smoke at <" .. dcsCommon.point2text(p, true) .. ">.", 30)
end
end
function debugDemon.processBoomCommand(args, event)
-- syntax -color
local power = 1 -- boom default
local powerCom = args[1]
if powerCom then
powerCom = tonumber(powerCom)
if powerCom then
power = powerCom
end
end
local pos = event.pos
local h = land.getHeight({x = pos.x, y = pos.z}) + 1
local p = { x = event.pos.x, y = h, z = event.pos.z}
trigger.action.explosion(p, power)
debugger.outText("*** boom: placed <" .. power .. "> explosion at <" .. dcsCommon.point2text(p, true) .. ">.", 30)
end
--
-- spawning units at the location of the mark
--
function debugDemon.getCoaFromCommand(args)
for i=1, #args do
local aParam = args[i]
if dcsCommon.stringStartsWith(aParam, "red", true) then return 1, i end
if dcsCommon.stringStartsWith(aParam, "blu", true) then return 2, i end
if dcsCommon.stringStartsWith(aParam, "neu", true) then return 0, i end
end
return 0, nil
end
function debugDemon.getAirFromCommand(args)
for i=1, #args do
local aParam = args[i]
if aParam:lower() == "inair" then return true, i end
if aParam:lower() == "air" then return true, i end
end
return false, nil
end
function debugDemon.getHeadingFromCommand(args)
for i=1, #args do
local aParam = args[i]
if dcsCommon.stringStartsWith(aParam, "heading=", true) then
local parts = dcsCommon.splitString(aParam, "=")
local num = parts[2]
if num and tonumber(num) then
return tonumber(num), i
end
end
end
return 0, nil
end
function debugDemon.getNumFromCommand(args)
for i=1, #args do
local aParam = args[i]
local num = tonumber(aParam)
if num then return num, i end
end
return 1, nil
end
function debugDemon.processSpawnCommand(args, event)
-- complex syntax:
-- spawn [red|blue|neutral] [number] <type> [heading=<number>] | "?"
local params = dcsCommon.clone(args)
-- for i=1, #params do
-- trigger.action.outText("arg[" .. i .."] = <" .. params[i] .. ">", 30)
-- end
-- get coalition from input
local coa, idx = debugDemon.getCoaFromCommand(params)
if idx then table.remove(params, idx) end
local inAir, idy = debugDemon.getAirFromCommand(params)
if idy then table.remove(params, idy) end
local num, idz = debugDemon.getNumFromCommand(params)
if idz then table.remove(params, idz) end
local heading, idk = debugDemon.getHeadingFromCommand(params)
if idk then table.remove(params, idk) end
local class = params[1]
if not class then
debugger.outText("*** spawn: missing keyword (what to spawn).", 30)
return
end
class = class:lower()
-- when we are here, we have reduced all params, so class is [1]
-- trigger.action.outText("spawn with class <" .. class .. ">, num <" .. num .. ">, inAir <" .. dcsCommon.bool2Text(inAir) .. ">, coa <" .. coa .. ">, hdg <" .. heading .. ">", 30)
heading = heading * 0.0174533 -- in rad
local pos = event.pos
local h = land.getHeight({x = pos.x, y = pos.z}) + 1
local p = { x = event.pos.x, y = h, z = event.pos.z}
if class == "tank" or class == "tanks" then
-- spawn the 'tank' class
local theType = debugger.spawnTypes["tank"]
return debugDemon.spawnTypeWithCat(theType, coa, num, p, nil,heading)
elseif class == "man" or class == "soldier" or class == "men" then
local theType = debugger.spawnTypes["inf"]
return debugDemon.spawnTypeWithCat(theType, coa, num, p, nil,heading)
elseif class == "inf" or class == "ifv" or class == "sam" or
class == "arty" or class == "aaa" then
local theType = debugger.spawnTypes[class]
return debugDemon.spawnTypeWithCat(theType, coa, num, p, nil,heading)
elseif class == "truck" or class == "trucks" then
local theType = debugger.spawnTypes["truck"]
return debugDemon.spawnTypeWithCat(theType, coa, num, p, nil,heading)
elseif class == "manpad" or class == "manpads" or class == "pad" or class == "pads" then
local theType = debugger.spawnTypes["manpad"]
return debugDemon.spawnTypeWithCat(theType, coa, num, p, nil,heading)
elseif class == "ship" or class == "ships" then
local theType = debugger.spawnTypes["ship"]
return debugDemon.spawnTypeWithCat(theType, coa, num, p, Group.Category.SHIP, heading)
elseif class == "jet" or class == "jets" then
local theType = debugger.spawnTypes["jet"]
return debugDemon.spawnAirWIthCat(theType, coa, num, p, nil, 1000, 160, heading)
elseif class == "ww2" then
local theType = debugger.spawnTypes[class]
return debugDemon.spawnAirWIthCat(theType, coa, num, p, nil, 1000, 100, heading)
elseif class == "bomber" or class == "awacs" then
local theType = debugger.spawnTypes[class]
return debugDemon.spawnAirWIthCat(theType, coa, num, p, nil, 8000, 200, heading)
elseif class == "drone" then
local theType = debugger.spawnTypes[class]
return debugDemon.spawnAirWIthCat(theType, coa, num, p, nil, 3000, 77, heading)
elseif class == "helo" or class == "helos" then
local theType = debugger.spawnTypes["helo"]
return debugDemon.spawnAirWIthCat(theType, coa, num, p, Group.Category.HELICOPTER, 200, 40, heading)
elseif class == "cargo" or class == "obj" then
local isCargo = (class == "cargo")
local theType = debugger.spawnTypes[class]
return debugDemon.spawnObjects(theType, coa, num, p, isCargo, heading)
elseif class == "?" then
local m = " spawn: invoke '-spawn [number] [coalition] <type> [heading]' with \n" ..
" number = any number, default is 1\n" ..
" coalition = 'red' | 'blue' | 'neutral', default is neutral\n" ..
" heading = 'heading=<number>' - direction to face, in degrees, no blanks\n" ..
" <type> = what to spawn, any of the following pre-defined (no quotes)\n" ..
" 'tank' - a tank " .. debugDemon.tellType("tank") .. "\n" ..
" 'ifv' - an IFV " .. debugDemon.tellType("ifv") .. "\n" ..
" 'inf' - an infantry soldier " .. debugDemon.tellType("inf") .. "\n" ..
" 'sam' - a SAM vehicle " .. debugDemon.tellType("sam") .. "\n" ..
" 'aaa' - a AAA vehicle " .. debugDemon.tellType("aaa") .. "\n" ..
" 'arty' - artillery vehicle " .. debugDemon.tellType("arty") .. "\n" ..
" 'manpad' - a soldier with SAM " .. debugDemon.tellType("manpad") .. "\n" ..
" 'truck' - a truck " .. debugDemon.tellType("truck") .. "\n\n" ..
" 'jet' - a fast aircraft " .. debugDemon.tellType("jet") .. "\n" ..
" 'ww2' - a warbird " .. debugDemon.tellType("ww2") .. "\n" ..
" 'bomber' - a heavy bomber " .. debugDemon.tellType("bomber") .. "\n" ..
" 'awacs' - an AWACS plane " .. debugDemon.tellType("awacs") .. "\n" ..
" 'drone' - a drone " .. debugDemon.tellType("drone") .. "\n" ..
" 'helo' - a helicopter " .. debugDemon.tellType("helo") .. "\n\n" ..
" 'ship' - a naval unit" .. debugDemon.tellType("ship") .. "\n\n" ..
" 'cargo' - some helicopter cargo " .. debugDemon.tellType("cargo") .. "\n" ..
" 'obj' - a static object " .. debugDemon.tellType("obj") .. "\n"
debugger.outText(m, 30)
return true
else
debugger.outText("*** spawn: unknown kind <" .. class .. ">.", 30)
return false
end
end
function debugDemon.tellType(theType)
return " [" .. debugger.spawnTypes[theType] .. "]"
end
function debugDemon.spawnTypeWithCat(theType, coa, num, p, cat, heading)
trigger.action.outText("heading is <" .. heading .. ">", 30)
if not cat then cat = Group.Category.GROUND end
if not heading then heading = 0 end
local xOff = 0
local yOff = 0
-- build group
local groupName = dcsCommon.uuid(theType)
local gData = dcsCommon.createEmptyGroundGroupData(groupName)
for i=1, num do
local aUnit = {}
aUnit = dcsCommon.createGroundUnitData(groupName .. "-" .. i, theType)
--aUnit.heading = heading
dcsCommon.addUnitToGroupData(aUnit, gData, xOff, yOff, heading)
xOff = xOff + 10
yOff = yOff + 10
end
-- arrange in a grid formation
local radius = math.floor(math.sqrt(num) * 10)
if cat == Group.Category.SHIP then
radius = math.floor(math.sqrt(num) * 100)
end
dcsCommon.arrangeGroupDataIntoFormation(gData, radius, 10, "GRID")
-- move to destination
dcsCommon.moveGroupDataTo(gData, p.x, p.z)
-- spawn
local cty = dcsCommon.getACountryForCoalition(coa)
local theGroup = coalition.addGroup(cty, cat, gData)
if theGroup then
debugger.outText("[" .. dcsCommon.nowString() .. "] created units at " .. dcsCommon.point2text(p, true), 30)
return true
else
debugger.outText("[" .. dcsCommon.nowString() .. "] failed to created units", 30)
return false
end
return false
end
function debugDemon.spawnAirWIthCat(theType, coa, num, p, cat, alt, speed, heading)
if not cat then cat = Group.Category.AIRPLANE end
local xOff = 0
local yOff = 0
-- build group
local groupName = dcsCommon.uuid(theType)
local gData = dcsCommon.createEmptyAircraftGroupData(groupName)
for i=1, num do
local aUnit = {}
aUnit = dcsCommon.createAircraftUnitData(groupName .. "-" .. i, theType, false, alt, speed)
--aUnit.heading = heading
dcsCommon.addUnitToGroupData(aUnit, gData, xOff, yOff, heading)
xOff = xOff + 30
yOff = yOff + 30
end
-- move to destination
dcsCommon.moveGroupDataTo(gData, p.x, p.z)
-- make waypoints: initial point and 200 km away in direction heading
local p2 = dcsCommon.pointInDirectionOfPointXYY(heading, 200000, p)
local wp1 = dcsCommon.createSimpleRoutePointData(p, alt, speed)
local wp2 = dcsCommon.createSimpleRoutePointData(p2, alt, speed)
-- add waypoints
dcsCommon.addRoutePointForGroupData(gData, wp1)
dcsCommon.addRoutePointForGroupData(gData, wp2)
-- spawn
local cty = dcsCommon.getACountryForCoalition(coa)
local theGroup = coalition.addGroup(cty, cat, gData)
if theGroup then
debugger.outText("[" .. dcsCommon.nowString() .. "] created air units at " .. dcsCommon.point2text(p, true), 30)
return true
else
debugger.outText("[" .. dcsCommon.nowString() .. "] failed to created air units", 30)
return false
end
end
function debugDemon.spawnObjects(theType, coa, num, p, cargo, heading)
if not cargo then cargo = false end
local cty = dcsCommon.getACountryForCoalition(coa)
local xOff = 0
local yOff = 0
local success = false
-- build static objects and spawn individually
for i=1, num do
local groupName = dcsCommon.uuid(theType)
local gData = dcsCommon.createStaticObjectData(groupName, theType, 0, false, cargo, 1000)
gData.x = xOff + p.x
gData.y = yOff + p.z
gData.heading = heading
local theGroup = coalition.addStaticObject(cty, gData)
success = theGroup
xOff = xOff + 10 -- stagger by 10m, 10m
yOff = yOff + 10
end
-- was it worth it?
if success then
debugger.outText("[" .. dcsCommon.nowString() .. "] created objects at " .. dcsCommon.point2text(p, true), 30)
return true
else
debugger.outText("[" .. dcsCommon.nowString() .. "] failed to create objects", 30)
return false
end
end
--
-- init and start
@ -1430,6 +1866,7 @@ function debugDemon.readConfigZone()
end
end
function debugDemon.init()
if not dcsCommon.libCheck then
trigger.action.outText("cfx interactive debugger requires dcsCommon", 30)
@ -1473,10 +1910,15 @@ function debugDemon.init()
debugDemon.addCommndProcessor("help", debugDemon.processHelpCommand)
debugDemon.addCommndProcessor("remove", debugDemon.processRemoveCommand)
debugDemon.addCommndProcessor("spawn", debugDemon.processSpawnCommand)
debugDemon.addCommndProcessor("add", debugDemon.processSpawnCommand)
debugDemon.addCommndProcessor("eventmon", debugDemon.processEventMonCommand)
debugDemon.addCommndProcessor("q", debugDemon.processQueryCommand)
debugDemon.addCommndProcessor("w", debugDemon.processWriteCommand)
debugDemon.addCommndProcessor("a", debugDemon.processAnalyzeCommand)
debugDemon.addCommndProcessor("smoke", debugDemon.processSmokeCommand)
debugDemon.addCommndProcessor("boom", debugDemon.processBoomCommand)
return true
end
@ -1513,13 +1955,8 @@ end
- exec files. save all commands and then run them from script
- query objects: -q persistence.active returns boolean, true
-q x.y returns table, 12 elements
-q a.b.x returns number 12
-q d.e.f returns string "asdasda..."
-q sada returs <nil>
- xref: which zones/attributes reference a flag, g.g. '-xref go'
- dml version can config to start with events list, e.g. 1, 4, 7
- track lua vars for change in value
--]]--

View File

@ -1,10 +1,10 @@
unitPersistence = {}
unitPersistence.version = '1.1.1'
unitPersistence.version = '2.0.0'
unitPersistence.verbose = false
unitPersistence.updateTime = 60 -- seconds. Once every minute check statics
unitPersistence.requiredLibs = {
"dcsCommon", -- always
"cfxZones", -- Zones, of course
"dcsCommon",
"cfxZones",
"persistence",
"cfxMX",
}
@ -19,7 +19,8 @@ unitPersistence.requiredLibs = {
1.1.0 - added air and sea units - for filtering destroyed units
1.1.1 - fixed static link (again)
- fixed air spawn (fixed wing)
2.0.0 - dmlZones, OOP
cleanup
REQUIRES PERSISTENCE AND MX

View File

@ -1,5 +1,5 @@
williePete = {}
williePete.version = "1.0.2"
williePete.version = "2.0.0"
williePete.ups = 10 -- we update at 10 fps, so accuracy of a
-- missile moving at Mach 2 is within 33 meters,
-- with interpolation even at 3 meters
@ -14,12 +14,16 @@ williePete.requiredLibs = {
1.0.0 - Initial version
1.0.1 - update to suppress verbosity
1.0.2 - added Gazelle WP
2.0.0 - dmlZones, OOP
- Guards for multi-unit player groups
- getFirstLivingPlayerInGroupNamed()
--]]--
williePete.willies = {}
williePete.wpZones = {}
williePete.playerGUIs = {} -- used for unit guis
williePete.groupGUIs = {} -- because some people may want to install
-- multip-unit player groups
williePete.blastedObjects = {} -- used when we detonate something
-- recognizes WP munitions. May require regular update when new
@ -90,30 +94,29 @@ end
function williePete.createWPZone(aZone)
aZone.coalition = cfxZones.getCoalitionFromZoneProperty(aZone, "wpTarget", 0) -- side that marks it on map, and who fires arty
aZone.shellStrength = cfxZones.getNumberFromZoneProperty(aZone, "shellStrength", 500) -- power of shells (strength)
aZone.shellNum = cfxZones.getNumberFromZoneProperty(aZone, "shellNum", 17) -- number of shells in bombardment
aZone.transitionTime = cfxZones.getNumberFromZoneProperty(aZone, "transitionTime", 20) -- average time of travel for projectiles
aZone.coolDown = cfxZones.getNumberFromZoneProperty(aZone, "coolDown", 180) -- cooldown after arty fire, used to set readyTime
aZone.baseAccuracy = cfxZones.getNumberFromZoneProperty(aZone, "baseAccuracy", 50)
aZone.coalition = aZone:getCoalitionFromZoneProperty("wpTarget", 0) -- side that marks it on map, and who fires arty
aZone.shellStrength = aZone:getNumberFromZoneProperty( "shellStrength", 500) -- power of shells (strength)
aZone.shellNum = aZone:getNumberFromZoneProperty("shellNum", 17) -- number of shells in bombardment
aZone.transitionTime = aZone:getNumberFromZoneProperty( "transitionTime", 20) -- average time of travel for projectiles
aZone.coolDown = aZone:getNumberFromZoneProperty("coolDown", 180) -- cooldown after arty fire, used to set readyTime
aZone.baseAccuracy = aZone:getNumberFromZoneProperty( "baseAccuracy", 50)
aZone.readyTime = 0 -- if readyTime > now we are not ready
aZone.trackingPlayer = nil -- name player's unit who is being tracked for wp. may not be neccessary
aZone.checkedIn = {} -- dict of all planes currently checked in
aZone.wpMethod = cfxZones.getStringFromZoneProperty(aZone, "wpMethod", "change")
aZone.checkInRange = cfxZones.getNumberFromZoneProperty(aZone, "checkInRange", williePete.checkInRange) -- default to my default
aZone.ackSound = cfxZones.getStringFromZoneProperty(aZone, "ackSound", williePete.ackSound)
aZone.guiSound = cfxZones.getStringFromZoneProperty(aZone, "guiSound", williePete.guiSound)
if cfxZones.hasProperty(aZone, "method") then
aZone.wpMethod = cfxZones.getStringFromZoneProperty(aZone, "method", "change")
aZone.wpMethod = aZone:getStringFromZoneProperty("wpMethod", "change")
if aZone:hasProperty("method") then
aZone.wpMethod = aZone:getStringFromZoneProperty("method", "change")
end
if cfxZones.hasProperty(aZone, "wpFire!") then
aZone.wpFire = cfxZones.getStringFromZoneProperty(aZone, "wpFire!", "<none)")
aZone.checkInRange = aZone:getNumberFromZoneProperty( "checkInRange", williePete.checkInRange) -- default to my default
aZone.ackSound = aZone:getStringFromZoneProperty("ackSound", williePete.ackSound)
aZone.guiSound = aZone:getStringFromZoneProperty("guiSound", williePete.guiSound)
if aZone:hasProperty("wpFire!") then
aZone.wpFire = aZone:getStringFromZoneProperty("wpFire!", "<none)")
end
if aZone.verbose then
@ -142,6 +145,7 @@ function williePete.startPlayerGUI()
end
unitInfo.name = uName -- needed for reverse-lookup
unitInfo.gName = gName -- also needed for reverse lookup
unitInfo.coa = coa
unitInfo.gID = gData.groupId
unitInfo.uID = uData.unitId
@ -159,12 +163,20 @@ function williePete.startPlayerGUI()
if pass then -- we install a menu for this group
-- we may not want check in stuff, but it could be cool
unitInfo.root = missionCommands.addSubMenuForGroup(unitInfo.gID, "FAC")
unitInfo.checkIn = missionCommands.addCommandForGroup(unitInfo.gID, "Check In", unitInfo.root, williePete.redirectCheckIn, unitInfo)
if williePete.playerGUIs[gName] then
trigger.action.outText("+++WP: Warning: we already have WP menu for unit <" .. uName .. "> in group <" .. gName .. ">. Skipped.", 30)
elseif williePete.groupGUIs[gName] then
trigger.action.outText("+++WP: Warning: POSSIBLE MULTI-PLAYER UNIT GROUP DETECTED. We already have WP menu for Player Group <" .. gName .. ">. Skipped, only first unit supported. ", 30)
else
unitInfo.root = missionCommands.addSubMenuForGroup(unitInfo.gID, "FAC")
unitInfo.checkIn = missionCommands.addCommandForGroup(unitInfo.gID, "Check In", unitInfo.root, williePete.redirectCheckIn, unitInfo)
williePete.groupGUIs[gName] = unitInfo
williePete.playerGUIs[gName] = unitInfo
end
end
-- store it
williePete.playerGUIs[uName] = unitInfo
-- store it - WARNING: ASSUMES SINGLE-UNIT Player Groups
--williePete.playerGUIs[uName] = unitInfo
end
end
@ -177,7 +189,7 @@ function williePete.doBoom(args)
-- note that unit who commânded fire may no longer be alive
-- so check it every time. unit must be alive
-- to receive credits later
local uName = unitInfo.name
local uName = unitInfo.gName
local blastRad = math.floor(math.sqrt(args.strength)) * 2
if blastRad < 10 then blastRad = 10 end
@ -187,7 +199,7 @@ function williePete.doBoom(args)
if williePete.verbose then
trigger.action.outText("<" .. aName .. "> is in blast Radius (" .. blastRad .. "m) of shells for <" .. uName .. ">'s target coords", 30)
end
williePete.blastedObjects[aName] = uName -- last one gets the kill
williePete.blastedObjects[aName] = unitInfo.name -- last one gets the kill
end
end
trigger.action.explosion(args.point, args.strength)
@ -237,16 +249,36 @@ function williePete.redirectCheckIn(unitInfo)
timer.scheduleFunction(williePete.doCheckIn, unitInfo, timer.getTime() + 0.1)
end
-- fix for multi-unit player groups where only one of them is
-- alive: get first living player in group. Will be added to
-- dcsCommon soon
function williePete.getFirstLivingPlayerInGroupNamed(gName)
local theGroup = Group.getByName(gName)
if not theGroup then return nil end
local theUnits = theGroup:getUnits()
for idx, aUnit in pairs(theUnits) do
if Unit.isExist(aUnit) and aUnit.getPlayerName and
aUnit:getPlayerName() then
return aUnit -- return first living player unit
end
end
return nil
end
function williePete.doCheckIn(unitInfo)
--trigger.action.outText("check-in received", 30)
local theUnit = Unit.getByName(unitInfo.name)
-- WARNING: unitInfo points to first processed player in
-- group. May not work fully with multi-unit player groups
local gName = unitInfo.gName
local theUnit = williePete.getFirstLivingPlayerInGroupNamed(gName) --Unit.getByName(unitInfo.name)
if not theUnit then
-- dead man calling. Pilot dead but unit still alive
-- OR second unit in multiplayer group, but unit 1
-- does not / no longer exists
trigger.action.outText("Calling station, say again, can't read you.", 30)
return
end
local p = theUnit:getPoint()
local p = theUnit:getPoint() -- only react to first player unit
local theZone, dist = williePete.closestCheckInTgtZoneForCoa(p, unitInfo.coa)
if not theZone then
@ -256,21 +288,23 @@ function williePete.doCheckIn(unitInfo)
trigger.action.outSoundForGroup(unitInfo.gID, williePete.guiSound)
return
end
trigger.action.outTextForGroup(unitInfo.gID, "Too far from target zone, closest target zone is " .. theZone.name, 30)
dist = math.floor(dist /100) / 10
bearing = dcsCommon.bearingInDegreesFromAtoB(p, theZone:getPoint())
trigger.action.outTextForGroup(unitInfo.gID, unitInfo.gName .. ", you are too far from target zone, closest target zone is " .. theZone.name .. ", " .. dist .. "km at bearing " .. bearing .. "°", 30)
trigger.action.outSoundForGroup(unitInfo.gID, theZone.guiSound)
return
end
-- we are now checked in to zone -- unless we are already checked in
if theZone.checkedIn[unitInfo.name] then
trigger.action.outTextForGroup(unitInfo.gID, unitInfo.name .. ", " .. theZone.name .. ", we heard you the first time, proceed.", 30)
-- NOTE: we use group name, not unit name!
if theZone.checkedIn[unitInfo.gName] then
trigger.action.outTextForGroup(unitInfo.gID, unitInfo.gName .. ", " .. theZone.name .. ", we heard you the first time, proceed.", 30)
trigger.action.outSoundForGroup(unitInfo.gID, theZone.guiSound)
return
end
-- we now check in
theZone.checkedIn[unitInfo.name] = unitInfo
theZone.checkedIn[unitInfo.gName] = unitInfo
-- add the 'Target marked' menu
unitInfo.targetMarked = missionCommands.addCommandForGroup(unitInfo.gID, "Target Marked, commence firing", unitInfo.root, williePete.redirectTargetMarked, unitInfo)
@ -280,7 +314,7 @@ function williePete.doCheckIn(unitInfo)
-- add 'check out'
unitInfo.checkOut = missionCommands.addCommandForGroup(unitInfo.gID, "Check Out of " .. theZone.name, unitInfo.root, williePete.redirectCheckOut, unitInfo)
trigger.action.outTextForGroup(unitInfo.gID, "Roger " .. unitInfo.name .. ", " .. theZone.name .. " tracks you, standing by for target data.", 30)
trigger.action.outTextForGroup(unitInfo.gID, "Roger " .. unitInfo.gName .. ", " .. theZone.name .. " tracks you, standing by for target data.", 30)
trigger.action.outSoundForGroup(unitInfo.gID, theZone.guiSound)
end
@ -293,17 +327,17 @@ function williePete.doCheckOut(unitInfo)
local wasCheckedIn = false
local fromZone = ""
for idx, theZone in pairs(williePete.wpZones) do
if theZone.checkedIn[unitInfo.name] then
if theZone.checkedIn[unitInfo.gName] then
wasCheckedIn = true
fromZone = theZone.name
end
theZone.checkedIn[unitInfo.name] = nil
theZone.checkedIn[unitInfo.gName] = nil
end
if not wasCheckedIn then
trigger.action.outTextForGroup(unitInfo.gID, unitInfo.name .. ", roger cecked-out. Good hunting!", 30)
trigger.action.outTextForGroup(unitInfo.gID, unitInfo.gName .. ", roger cecked-out. Good hunting!", 30)
trigger.action.outSoundForGroup(unitInfo.gID, williePete.guiSound)
else
trigger.action.outTextForGroup(unitInfo.gID, unitInfo.name .. "has checked out of " .. fromZone ..".", 30)
trigger.action.outTextForGroup(unitInfo.gID, unitInfo.gName .. " has checked out of " .. fromZone ..".", 30)
trigger.action.outSoundForGroup(unitInfo.gID, williePete.guiSound)
end
@ -326,7 +360,7 @@ function williePete.rogerDodger(args)
local unitInfo = args[1]
local theZone = args[2]
trigger.action.outTextForCoalition(unitInfo.coa, "Roger " .. unitInfo.name .. ", good copy, firing.", 30)
trigger.action.outTextForCoalition(unitInfo.coa, "Roger " .. unitInfo.gName .. ", good copy, firing.", 30)
trigger.action.outSoundForCoalition(unitInfo.coa, theZone.ackSound)
end
@ -357,9 +391,9 @@ function williePete.doTargetMarked(unitInfo)
local tgtZone = unitInfo.wpInZone
-- see if we are checked into that zone
if not tgtZone.checkedIn[unitInfo.name] then
if not tgtZone.checkedIn[unitInfo.gName] then
-- zones don't match
trigger.action.outTextForGroup(unitInfo.gID, "Say again " .. unitInfo.name .. ", we have crosstalk. Try and reset coms", 30)
trigger.action.outTextForGroup(unitInfo.gID, "Say again " .. unitInfo.gName .. ", we have crosstalk. Try and reset coms", 30)
trigger.action.outSoundForGroup(unitInfo.gID, williePete.guiSound)
return
end
@ -368,7 +402,7 @@ function williePete.doTargetMarked(unitInfo)
local timeRemaining = math.floor(tgtZone.readyTime - now)
if timeRemaining > 0 then
-- zone not ready
trigger.action.outTextForGroup(unitInfo.gID, "Stand by " .. unitInfo.name .. ", artillery not ready. Expect " .. timeRemaining + math.random(1, 5) .. " seconds.", 30)
trigger.action.outTextForGroup(unitInfo.gID, "Stand by " .. unitInfo.gName .. ", artillery not ready. Expect " .. timeRemaining + math.random(1, 5) .. " seconds.", 30)
trigger.action.outSoundForGroup(unitInfo.gID, tgtZone.guiSound)
return
end
@ -378,7 +412,7 @@ function williePete.doTargetMarked(unitInfo)
local grid = coord.LLtoMGRS(coord.LOtoLL(unitInfo.pos))
local mgrs = grid.UTMZone .. ' ' .. grid.MGRSDigraph .. ' ' .. grid.Easting .. ' ' .. grid.Northing
local theLoc = mgrs
trigger.action.outTextForCoalition(unitInfo.coa, tgtZone.name ..", " .. unitInfo.name .." is transmitting target location. Fire at " .. theLoc .. ", elevation " .. alt .. " meters, target marked.", 30)
trigger.action.outTextForCoalition(unitInfo.coa, tgtZone.name ..", " .. unitInfo.gName .." is transmitting target location. Fire at " .. theLoc .. ", elevation " .. alt .. " meters, target marked.", 30)
trigger.action.outSoundForCoalition(unitInfo.coa, tgtZone.guiSound)
timer.scheduleFunction(williePete.rogerDodger, {unitInfo, tgtZone},timer.getTime() + math.random(2, 5))
@ -401,10 +435,13 @@ end
-- return true if a zone is actively tracking theUnit to place
-- a wp
function williePete.zoneIsTracking(theUnit)
function williePete.zoneIsTracking(theUnit) -- group level!
local uName = theUnit:getName()
local uGroup = theUnit:getGroup()
local gName = uGroup:getName()
for idx, theZone in pairs(williePete.wpZones) do
if theZone.checkedIn[uName] then return true end
if theZone.checkedIn[gName] then return true end
end
return false
end
@ -426,6 +463,8 @@ function williePete.zedsDead(theObject)
local theName = theObject:getName()
-- now check if it's a registered blasted object:getSampleRate()
-- in multi-unit player groups, this can can lead to
-- mis-attribution, beware!
local blaster = williePete.blastedObjects[theName]
if blaster then
local theUnit = Unit.getByName(blaster)
@ -459,7 +498,7 @@ function williePete:onEvent(event)
local theUnit = event.initiator
local pType = "(AI)"
if theUnit.getPlayerName then pType = "(" .. theUnit:getName() .. ")" end
if theUnit.getPlayerName and theUnit:getPlayerName() then pType = "(" .. theUnit:getName() .. ")" end
if event.id == 1 then -- S_EVENT_SHOT
-- initiator is who fired. maybe want to test if player
@ -470,7 +509,7 @@ function williePete:onEvent(event)
end
-- make sure that whoever fired it is being tracked by
-- a zone
-- a zone. zoneIsTracking checks on GROUP level!
if not williePete.zoneIsTracking(theUnit) then
return
end
@ -479,6 +518,8 @@ function williePete:onEvent(event)
local theWillie = {}
theWillie.firedBy = theUnit:getName()
theWillie.theUnit = theUnit
theWillie.theGroup = theUnit:getGroup()
theWillie.gName = theWillie.theGroup:getName()
theWillie.weapon = event.weapon
theWillie.wt = theWillie.weapon:getTypeName()
theWillie.pos = theWillie.weapon:getPoint()
@ -487,15 +528,6 @@ function williePete:onEvent(event)
williePete.addWillie(theWillie)
end
--[[--
if event.id == 2 then -- hit
local what = "something"
if event.target then what = event.target:getName() end
--trigger.action.outText("Weapon " .. event.weapon:getTypeName() .. " fired by unit ".. theUnit:getName() .. " " .. pType .. " hit " .. what, 30)
-- may need to remove willie from willies
end
--]]--
end
-- test if a projectile has hit the ground inside a wp zone
@ -505,17 +537,15 @@ function williePete.isInside(theWillie)
local theUnit = Unit.getByName(theUnitName)
if not theUnit then return false end -- unit dead
if not Unit.isExist(theUnit) then return false end -- dito
local thePlayer = williePete.playerGUIs[theUnitName]
if not thePlayer then return nil end
local theGroup = theUnit:getGroup()
local gName = theGroup:getName()
local unitInfo = williePete.groupGUIs[gName] -- returns unitInfo struct, contains group info
if not unitInfo then return nil end
for idx, theZone in pairs(williePete.wpZones) do
if cfxZones.pointInZone(thePoint, theZone) then
-- we are inside. but is this the right coalition?
if thePlayer.coa == theZone.coalition then
--trigger.action.outText("Willie in " .. theZone.name, 30)
if unitInfo.coa == theZone.coalition then
return theZone
else
--trigger.action.outText("Willie wrong coa", 30)
end
-- if we want to allow neutral zones (doens't make sense)
-- add another guard below
@ -532,8 +562,9 @@ function williePete.projectileHit(theWillie)
local vmod = dcsCommon.vMultScalar(theWillie.v, 0.5 / williePete.ups)
theWillie.pos = dcsCommon.vAdd(theWillie.pos, vmod)
-- reset last mark for player
local thePlayer = williePete.playerGUIs[theWillie.firedBy]
-- reset last mark for player's group
-- access unitInfo
local thePlayer = williePete.playerGUIs[theWillie.gName]
thePlayer.pos = nil
thePlayer.wpInZone = nil
@ -583,20 +614,25 @@ function williePete.playerUpdate()
-- the zone that they checked in, or they are checked out
--local zp = cfxZones.getPoint(theZone)
for idy, unitInfo in pairs(theZone.checkedIn) do
-- make sure unit still exists
-- make sure at least one unit still exists
local dropUnit = true
local theUnit = Unit.getByName(unitInfo.name)
if theUnit and Unit.isExist(theUnit) then
local up = theUnit:getPoint()
up.y = 0
local isInside, dist = cfxZones.isPointInsideZone(up, theZone, theZone.checkInRange)
local theGroup = Group.getByName(unitInfo.gName)
local allUnits = theGroup:getUnits()
for idx, theUnit in pairs(allUnits) do
--local theUnit = Unit.getByName(unitInfo.name)
if theUnit and Unit.isExist(theUnit) and
theUnit.getPlayerName and theUnit:getPlayerName() then
local up = theUnit:getPoint()
up.y = 0
local isInside, dist = cfxZones.isPointInsideZone(up, theZone, theZone.checkInRange)
if isInside then
dropUnit = false
if isInside then
dropUnit = false
end
end
end
if dropUnit then
-- remove from zone check-in
-- all outside, remove from zone check-in
-- williePete.doCheckOut(unitInfo)
timer.scheduleFunction(williePete.doCheckOut, unitInfo, timer.getTime() + 0.1) -- to not muck up iteration
end
@ -612,13 +648,10 @@ end
function williePete.readConfigZone()
local theZone = cfxZones.getZoneByName("wpConfig")
if not theZone then
if williePete.verbose then
trigger.action.outText("+++wp: NO config zone!", 30)
end
theZone = cfxZones.createSimpleZone("wpConfig")
end
local facTypes = cfxZones.getStringFromZoneProperty(theZone, "facTypes", "all")
local facTypes = theZone:getStringFromZoneProperty("facTypes", "all")
facTypes = string.upper(facTypes)
-- make this an array
@ -631,19 +664,19 @@ function williePete.readConfigZone()
williePete.facTypes = dcsCommon.trimArray(allTypes)
-- how long a wp is active. must not be more than 5 minutes
williePete.wpMaxTime = cfxZones.getNumberFromZoneProperty(theZone, "wpMaxTime", 3 * 60)
williePete.wpMaxTime = theZone:getNumberFromZoneProperty( "wpMaxTime", 3 * 60)
-- default check-in range, added to target zone's range and used
-- for auto-check-out
williePete.checkInRange = cfxZones.getNumberFromZoneProperty(theZone, "checkInRange", 10000) -- 10 km outside
williePete.checkInRange = theZone:getNumberFromZoneProperty("checkInRange", 10000) -- 10 km outside
williePete.ackSound = cfxZones.getStringFromZoneProperty(theZone, "ackSound", "some")
williePete.guiSound = cfxZones.getStringFromZoneProperty(theZone, "guiSound", "some")
williePete.ackSound = theZone:getStringFromZoneProperty( "ackSound", "some")
williePete.guiSound = theZone:getStringFromZoneProperty( "guiSound", "some")
williePete.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false)
williePete.verbose = theZone.verbose
if williePete.verbose then
trigger.action.outText("+++msgr: read config", 30)
trigger.action.outText("+++wp: read config", 30)
end
end

View File

@ -1,5 +1,5 @@
xFlags = {}
xFlags.version = "1.3.1"
xFlags.version = "2.0.0"
xFlags.verbose = false
xFlags.hiVerbose = false
xFlags.ups = 1 -- overwritten in get config when configZone is present
@ -11,30 +11,11 @@ xFlags.requiredLibs = {
xFlags - flag array transmogrifier
Version History
1.0.0 - Initial version
1.0.1 - allow flags names for ops as well
1.1.0 - Watchflags harmonization
1.2.0 - xDirect flag,
- direct array support
1.2.1 - verbosity changes
- "most" operator
- "half or more" operator
- fixed reset
- xSuccess optimizations
- inc, dec, quoted flags
- matchNum can carry flag
1.2.2 - on/off/suspend commands
- hiVerbose option
- corrected bug in reset checksum
1.3.0 - xCount! flag
- "never" operator
1.3.1 - guards for xtriggerOffFlag and xtriggerOnFlag
to prevent QoL warnings
- guards for xDirect
- guards for xCount
2.0.0 - dmlZones
- OOP
- xDirect#
- xCount#
- cleanup
--]]--
xFlags.xFlagZones = {}
@ -62,11 +43,7 @@ end
function xFlags.createXFlagsWithZone(theZone)
local theArray = ""
if cfxZones.hasProperty(theZone, "xFlags") then
theArray = cfxZones.getStringFromZoneProperty(theZone, "xFlags", "<none>")
else
theArray = cfxZones.getStringFromZoneProperty(theZone, "xFlags?", "<none>")
end
theArray = theZone:getStringFromZoneProperty("xFlags?", "<none>")
-- now process the array and create the value arrays
theZone.flagNames = cfxZones.flagArrayFromString(theArray)
@ -86,95 +63,72 @@ function xFlags.createXFlagsWithZone(theZone)
end
end
theZone.xHasFired = false
if cfxZones.hasProperty(theZone, "xSuccess!") then
theZone.xSuccess = cfxZones.getStringFromZoneProperty(theZone, "xSuccess!", "<none>")
end
if cfxZones.hasProperty(theZone, "out!") then
theZone.xSuccess = cfxZones.getStringFromZoneProperty(theZone, "out!", "*<none>")
if theZone:hasProperty("xSuccess!") then
theZone.xSuccess = theZone:getStringFromZoneProperty("xSuccess!", "<none>")
elseif theZone:hasProperty("out!") then
theZone.xSuccess = theZone:getStringFromZoneProperty("out!", "*<none>")
end
if not theZone.xSuccess then
theZone.xSuccess = "*<none>"
end
if cfxZones.hasProperty(theZone, "xChange!") then
theZone.xChange = cfxZones.getStringFromZoneProperty(theZone, "xChange!", "*<none>")
if theZone:hasProperty("xChange!") then
theZone.xChange = theZone:getStringFromZoneProperty("xChange!", "*<none>")
end
if cfxZones.hasProperty(theZone, "xDirect") then
theZone.xDirect = cfxZones.getStringFromZoneProperty(theZone, "xDirect", "*<none>")
if theZone:hasProperty("xDirect#") then
theZone.xDirect = theZone:getStringFromZoneProperty("xDirect#", "*<none>")
end
if cfxZones.hasProperty(theZone, "xCount") then
theZone.xCount = cfxZones.getStringFromZoneProperty(theZone, "xCount", "*<none>")
if theZone:hasProperty("xCount#") then
theZone.xCount = theZone:getStringFromZoneProperty("xCount#", "*<none>")
end
theZone.inspect = cfxZones.getStringFromZoneProperty(theZone, "require", "or") -- same as any
theZone.inspect = theZone:getStringFromZoneProperty("require", "or") -- same as any
-- supported any/or, all/and, moreThan, atLeast, exactly
theZone.inspect = string.lower(theZone.inspect)
theZone.inspect = dcsCommon.trim(theZone.inspect)
theZone.matchNum = cfxZones.getStringFromZoneProperty(theZone, "#hits", "1")
theZone.xTriggerMethod = cfxZones.getStringFromZoneProperty(theZone, "xFlagMethod", "change") -- (<>=[number or reference flag], off, on, yes, no, true, false, change
theZone.matchNum = theZone:getStringFromZoneProperty("#hits", "1") -- string because can also be a flag ref
theZone.xTriggerMethod = theZone:getStringFromZoneProperty( "xFlagMethod", "change")
theZone.xTriggerMethod = string.lower(theZone.xTriggerMethod)
theZone.xTriggerMethod = dcsCommon.trim(theZone.xTriggerMethod)
if cfxZones.hasProperty(theZone, "xReset?") then
theZone.xReset = cfxZones.getStringFromZoneProperty(theZone, "xReset?", "<none>")
theZone.xLastReset = cfxZones.getFlagValue(theZone.xReset, theZone)
if theZone:hasProperty("xReset?") then
theZone.xReset = theZone:getStringFromZoneProperty("xReset?", "<none>")
theZone.xLastReset = theZone:getFlagValue(theZone.xReset)
end
theZone.xMethod = cfxZones.getStringFromZoneProperty(theZone, "xMethod", "inc")
if cfxZones.hasProperty(theZone, "method") then
theZone.xMethod = cfxZones.getStringFromZoneProperty(theZone, "method", "inc")
theZone.xMethod = theZone:getStringFromZoneProperty("xMethod", "inc")
if theZone:hasProperty("method") then
theZone.xMethod = theZone:getStringFromZoneProperty("method", "inc")
end
theZone.xOneShot = cfxZones.getBoolFromZoneProperty(theZone, "oneShot", true)
theZone.xOneShot = theZone:getBoolFromZoneProperty("oneShot", true)
-- on / off commands
-- on/off flags
theZone.xSuspended = cfxZones.getBoolFromZoneProperty(theZone, "xSuspended", false) -- we are turned on
theZone.xSuspended = theZone:getBoolFromZoneProperty("xSuspended", false) -- we are turned on
if theZone.xSuspended and (xFlags.verbose or theZone.verbose) then
trigger.action.outText("+++xFlg: <" .. theZone.name .. "> starts suspended", 30)
end
if cfxZones.hasProperty(theZone, "xOn?") then
theZone.xtriggerOnFlag = cfxZones.getStringFromZoneProperty(theZone, "xOn?", "*<none1>")
theZone.xlastTriggerOnValue = cfxZones.getFlagValue(theZone.xtriggerOnFlag, theZone)
if theZone:hasProperty("xOn?") then
theZone.xtriggerOnFlag = theZone:getStringFromZoneProperty("xOn?", "*<none1>")
theZone.xlastTriggerOnValue = theZone:getFlagValue(theZone.xtriggerOnFlag)
end
if cfxZones.hasProperty(theZone, "xOff?") then
theZone.xtriggerOffFlag = cfxZones.getStringFromZoneProperty(theZone, "xOff?", "*<none2>")
theZone.xlastTriggerOffValue = cfxZones.getFlagValue(theZone.xtriggerOffFlag, theZone)
if theZone:hasProperty("xOff?") then
theZone.xtriggerOffFlag = theZone:getStringFromZoneProperty( "xOff?", "*<none2>")
theZone.xlastTriggerOffValue = theZone:getFlagValue(theZone.xtriggerOffFlag)
end
end
function xFlags.evaluateNumOrFlag(theAttribute, theZone)
-- on entry, theAttribute contains a string
-- if it's a number, we return that, if it's a
-- string, we see if it's a quoted flag or
-- direct flag. in any way, we fetch and return
-- that flag's value
local aNum = tonumber(theAttribute)
if aNum then return aNum end
local remainder = dcsCommon.trim(theAttribute)
local esc = string.sub(remainder, 1, 1)
local last = string.sub(remainder, -1)
if esc == "(" and last == ")" and string.len(remainder) > 2 then
remainder = string.sub(remainder, 2, -2)
remainder = dcsCommon.trim(remainder)
end
if esc == "\"" and last == "\"" and string.len(remainder) > 2 then
remainder = string.sub(remainder, 2, -2)
remainder = dcsCommon.trim(remainder)
end
rNum = cfxZones.getFlagValue(remainder, theZone)
return cfxZones.evalRemainder(theAttribute, theZone)
end
function xFlags.evaluateFlags(theZone)
@ -184,7 +138,7 @@ function xFlags.evaluateFlags(theZone)
-- since the checksum is order dependent,
-- we must preserve the order of the array
local flagName = theZone.flagNames[i]
currVals[i] = cfxZones.getFlagValue(flagName, theZone)
currVals[i] = theZone:getFlagValue(flagName)
end
-- now perform comparison flag by flag
@ -194,37 +148,10 @@ function xFlags.evaluateFlags(theZone)
local firstChar = string.sub(op, 1, 1)
local remainder = string.sub(op, 2)
remainder = dcsCommon.trim(remainder) -- remove all leading and trailing spaces
local rNum = tonumber(remainder)
if not rNum then
-- interpret remainder as flag name
-- so we can say >*killMax or "22" with 22 a flag name
-- we use remainder as name for flag
-- PROCESS ESCAPE SEQUENCES
local esc = string.sub(remainder, 1, 1)
local last = string.sub(remainder, -1)
if esc == "@" then
remainder = string.sub(remainder, 2)
remainder = dcsCommon.trim(remainder)
end
local rNum = theZone:evalRemainder(remainder)
if esc == "(" and last == ")" and string.len(remainder) > 2 then
-- note: iisues with startswith("(") ???
remainder = string.sub(remainder, 2, -2)
remainder = dcsCommon.trim(remainder)
end
if esc == "\"" and last == "\"" and string.len(remainder) > 2 then
remainder = string.sub(remainder, 2, -2)
remainder = dcsCommon.trim(remainder)
end
if cfxZones.verbose then
trigger.action.outText("+++zne: accessing flag <" .. remainder .. ">", 30)
end
rNum = cfxZones.getFlagValue(remainder, theZone)
end
-- this mimics cfxZones.testFlagByMethodForZone method (and is
-- the following mimics cfxZones.testFlagByMethodForZone method (and is
-- that method's genesis), but is different enough not to invoke that
-- method
for i = 1, #theZone.flagNames do
@ -298,7 +225,6 @@ function xFlags.evaluateFlags(theZone)
return 0, ""
end
if xFlags.verbose and lastHits ~= hits then
--trigger.action.outText("+++xF: hit detected for " .. theZone.flagNames[i] .. " in " .. theZone.name .. "(" .. op .. ")", 30)
end
end
return hits, checkSum
@ -361,7 +287,7 @@ function xFlags.evaluateZone(theZone)
end
if theZone.xChange then
cfxZones.pollFlag(theZone.xChange, theZone.xMethod, theZone)
theZone:pollFlag(theZone.xChange, theZone.xMethod)
if xFlags.verbose then
trigger.action.outText("+++xFlag: change bang! on " .. theZone.xChange .. " for " .. theZone.name, 30)
end
@ -378,15 +304,15 @@ function xFlags.evaluateZone(theZone)
-- true (1)/false(0), no matter if changed or not
if theZone.xDirect then
if evalResult then
cfxZones.setFlagValueMult(theZone.xDirect, 1, theZone)
theZone:setFlagValue(theZone.xDirect, 1)
else
cfxZones.setFlagValueMult(theZone.xDirect, 0, theZone)
theZone:setFlagValue(theZone.xDirect, 0)
end
end
-- directly set the xCount flag
if theZone.xCount then
cfxZones.setFlagValueMult(theZone.xCount, hits, theZone)
theZone:setFlagValueMult(theZone.xCount, hits)
end
-- now see if we bang the output according to method
@ -394,7 +320,7 @@ function xFlags.evaluateZone(theZone)
if xFlags.verbose or theZone.verbose then
trigger.action.outText("+++xFlag: success bang! on <" .. theZone.xSuccess .. "> for <" .. theZone.name .. "> with method <" .. theZone.xMethod .. ">", 30)
end
cfxZones.pollFlag(theZone.xSuccess, theZone.xMethod, theZone)
theZone:pollFlag(theZone.xSuccess, theZone.xMethod)
theZone.xHasFired = true
end
end
@ -407,14 +333,14 @@ function xFlags.update()
for idx, theZone in pairs (xFlags.xFlagZones) do
-- see if we should suspend
if theZone.xtriggerOnFlag and cfxZones.testZoneFlag(theZone, theZone.xtriggerOnFlag, "change", "xlastTriggerOnValue") then
if theZone.xtriggerOnFlag and theZone:testZoneFlag( theZone.xtriggerOnFlag, "change", "xlastTriggerOnValue") then
if xFlags.verbose or theZone.verbose then
trigger.action.outText("+++xFlg: enabling " .. theZone.name, 30)
end
theZone.xSuspended = false
end
if theZone.xtriggerOffFlag and cfxZones.testZoneFlag(theZone, theZone.xtriggerOffFlag, "change", "xlastTriggerOffValue") then
if theZone.xtriggerOffFlag and theZone:testZoneFlag( theZone.xtriggerOffFlag, "change", "xlastTriggerOffValue") then
if xFlags.verbose or theZone.verbose then
trigger.action.outText("+++xFlg: DISabling " .. theZone.name, 30)
end
@ -428,7 +354,7 @@ function xFlags.update()
-- see if they should reset
if theZone.xReset then
local currVal = cfxZones.getFlagValue(theZone.xReset, theZone)
local currVal = theZone:getFlagValue(theZone.xReset)
if currVal ~= theZone.xLastReset then
theZone.xLastReset = currVal
if xFlags.verbose or theZone.verbose then
@ -446,14 +372,11 @@ function xFlags.readConfigZone()
-- note: must match exactly!!!!
local theZone = cfxZones.getZoneByName("xFlagsConfig")
if not theZone then
if xFlags.verbose then
trigger.action.outText("***xFlag: NO config zone!", 30)
end
return
theZone = cfxZones.createSimpleZone("xFlagsConfig")
end
xFlags.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false)
xFlags.ups = cfxZones.getNumberFromZoneProperty(theZone, "ups", 1)
xFlags.verbose = theZone.verbose
xFlags.ups = theZone:getNumberFromZoneProperty("ups", 1)
if xFlags.verbose then
trigger.action.outText("***xFlg: read config", 30)
@ -476,9 +399,6 @@ function xFlags.start()
-- process RND Zones
local attrZones = cfxZones.getZonesWithAttributeNamed("xFlags")
-- now create an rnd gen for each one and add them
-- to our watchlist
for k, aZone in pairs(attrZones) do
xFlags.createXFlagsWithZone(aZone) -- process attribute and add to zone
xFlags.addxFlags(aZone) -- remember it
@ -509,4 +429,5 @@ end
--[[--
Additional features:
- make #hits compatible to flags and numbers
- autoReset -- can be done by short-circuiting xsuccess! into xReset?
--]]--