Version 1.1.0

Persistence
This commit is contained in:
Christian Franz 2022-07-28 10:41:30 +02:00
parent 780797aba3
commit f741c511a5
15 changed files with 1419 additions and 146 deletions

Binary file not shown.

Binary file not shown.

View File

@ -1,5 +1,5 @@
rndFlags = {} rndFlags = {}
rndFlags.version = "1.3.1" rndFlags.version = "1.3.2"
rndFlags.verbose = false rndFlags.verbose = false
rndFlags.requiredLibs = { rndFlags.requiredLibs = {
"dcsCommon", -- always "dcsCommon", -- always
@ -29,8 +29,10 @@ rndFlags.requiredLibs = {
- added zonal verbosity - added zonal verbosity
- added 'rndDone!' flag - added 'rndDone!' flag
- rndMethod defaults to "inc" - rndMethod defaults to "inc"
1.3.2 - moved flagArrayFromString to dcsCommon
--]] --]]
rndFlags.rndGen = {} rndFlags.rndGen = {}
function rndFlags.addRNDZone(aZone) function rndFlags.addRNDZone(aZone)
@ -38,6 +40,8 @@ function rndFlags.addRNDZone(aZone)
end end
function rndFlags.flagArrayFromString(inString) function rndFlags.flagArrayFromString(inString)
return dcsCommon.flagArrayFromString(inString, rndFlags.verbose)
--[[--
if string.len(inString) < 1 then if string.len(inString) < 1 then
trigger.action.outText("+++RND: empty flags", 30) trigger.action.outText("+++RND: empty flags", 30)
return {} return {}
@ -88,6 +92,7 @@ function rndFlags.flagArrayFromString(inString)
trigger.action.outText("+++RND: <" .. #flags .. "> flags total", 30) trigger.action.outText("+++RND: <" .. #flags .. "> flags total", 30)
end end
return flags return flags
--]]--
end end
-- --

View File

@ -1,5 +1,5 @@
cfxCargoReceiver = {} cfxCargoReceiver = {}
cfxCargoReceiver.version = "1.2.1" cfxCargoReceiver.version = "1.2.2"
cfxCargoReceiver.ups = 1 -- once a second 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.maxDirectionRange = 500 -- in m. distance when cargo manager starts talking to pilots who are carrying that cargo
cfxCargoReceiver.requiredLibs = { cfxCargoReceiver.requiredLibs = {
@ -17,6 +17,9 @@ cfxCargoReceiver.requiredLibs = {
- 1.2.0 method - 1.2.0 method
f!, cargoReceived! f!, cargoReceived!
- 1.2.1 cargoMethod - 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
CargoReceiver is a zone enhancement you use to be automatically CargoReceiver is a zone enhancement you use to be automatically
@ -39,13 +42,15 @@ function cfxCargoReceiver.processReceiverZone(aZone) -- process attribute and ad
-- isCargoReceiver flag and we are good -- isCargoReceiver flag and we are good
aZone.isCargoReceiver = true aZone.isCargoReceiver = true
-- we can add additional processing here -- we can add additional processing here
aZone.autoRemove = cfxZones.getBoolFromZoneProperty(aZone, "autoRemove", false) -- maybe add a removedelay aZone.autoRemove = cfxZones.getBoolFromZoneProperty(aZone, "autoRemove", false) -- maybe add a removeDelay
aZone.removeDelay = cfxZones.getNumberFromZoneProperty(aZone, "removeDelay", 1)
if aZone.removeDelay < 1 then aZone.removeDelay = 1 end
aZone.silent = cfxZones.getBoolFromZoneProperty(aZone, "silent", false) aZone.silent = cfxZones.getBoolFromZoneProperty(aZone, "silent", false)
--trigger.action.outText("+++rcv: recognized receiver zone: " .. aZone.name , 30) --trigger.action.outText("+++rcv: recognized receiver zone: " .. aZone.name , 30)
-- same integration as object destruct detector for flags -- same integration as object destruct detector for flags
--[[--
if cfxZones.hasProperty(aZone, "setFlag") then if cfxZones.hasProperty(aZone, "setFlag") then
aZone.setFlag = cfxZones.getStringFromZoneProperty(aZone, "setFlag", "999") aZone.setFlag = cfxZones.getStringFromZoneProperty(aZone, "setFlag", "999")
end end
@ -70,7 +75,7 @@ function cfxCargoReceiver.processReceiverZone(aZone) -- process attribute and ad
if cfxZones.hasProperty(aZone, "f-1") then if cfxZones.hasProperty(aZone, "f-1") then
aZone.decreaseFlag = cfxZones.getStringFromZoneProperty(aZone, "f-1", "999") aZone.decreaseFlag = cfxZones.getStringFromZoneProperty(aZone, "f-1", "999")
end end
--]]--
-- new method support -- new method support
aZone.cargoMethod = cfxZones.getStringFromZoneProperty(aZone, "method", "inc") aZone.cargoMethod = cfxZones.getStringFromZoneProperty(aZone, "method", "inc")
if cfxZones.hasProperty(aZone, "cargoMethod") then if cfxZones.hasProperty(aZone, "cargoMethod") then
@ -79,9 +84,7 @@ function cfxCargoReceiver.processReceiverZone(aZone) -- process attribute and ad
if cfxZones.hasProperty(aZone, "f!") then if cfxZones.hasProperty(aZone, "f!") then
aZone.outReceiveFlag = cfxZones.getStringFromZoneProperty(aZone, "f!", "*<none>") aZone.outReceiveFlag = cfxZones.getStringFromZoneProperty(aZone, "f!", "*<none>")
end elseif cfxZones.hasProperty(aZone, "cargoReceived!") then
if cfxZones.hasProperty(aZone, "cargoReceived!") then
aZone.outReceiveFlag = cfxZones.getStringFromZoneProperty(aZone, "cargoReceived!", "*<none>") aZone.outReceiveFlag = cfxZones.getStringFromZoneProperty(aZone, "cargoReceived!", "*<none>")
end end
@ -112,7 +115,25 @@ end
-- --
-- cargo event happened. Called by Cargo Manager -- cargo event happened. Called by Cargo Manager
-- --
function cfxCargoReceiver.removeCargo(args)
-- asynch call
if not args then return end
local theObject = args.theObject
local theZone = args.theZone
if not theObject then return end
if not theObject:isExist() then
-- maybe blew up? anyway, we are done
return
end
if args.theZone.verbose or cfxCargoReceiver.verbose then
trigger.action.outText("+++crgR: removed object <" .. theObject.getName() .. "> from cargo zone <" .. theZone.name .. ">", 30)
end
theObject:destroy()
end
function cfxCargoReceiver.cargoEvent(event, object, name) function cfxCargoReceiver.cargoEvent(event, object, name)
-- usually called from cargomanager
--trigger.action.outText("Cargo Receiver: event <" .. event .. "> for " .. name, 30) --trigger.action.outText("Cargo Receiver: event <" .. event .. "> for " .. name, 30)
if not event then return end if not event then return end
if event == "grounded" then if event == "grounded" then
@ -135,6 +156,7 @@ function cfxCargoReceiver.cargoEvent(event, object, name)
cfxCargoReceiver.invokeCallback("deliver", object, name, aZone) cfxCargoReceiver.invokeCallback("deliver", object, name, aZone)
-- set flags as indicated -- set flags as indicated
--[[--
if aZone.setFlag then if aZone.setFlag then
trigger.action.setUserFlag(aZone.setFlag, 1) trigger.action.setUserFlag(aZone.setFlag, 1)
end end
@ -149,16 +171,20 @@ function cfxCargoReceiver.cargoEvent(event, object, name)
local val = trigger.misc.getUserFlag(aZone.decreaseFlag) - 1 local val = trigger.misc.getUserFlag(aZone.decreaseFlag) - 1
trigger.action.setUserFlag(aZone.decreaseFlag, val) trigger.action.setUserFlag(aZone.decreaseFlag, val)
end end
--]]--
if aZone.outReceiveFlag then if aZone.outReceiveFlag then
cfxZones.pollFlag(aZone.outReceiveFlag, aZone.cargoMethod) cfxZones.pollFlag(aZone.outReceiveFlag, aZone.cargoMethod, aZone)
end end
--trigger.action.outText("+++rcv: " .. name .. " delivered in zone " .. aZone.name, 30) --trigger.action.outText("+++rcv: " .. name .. " delivered in zone " .. aZone.name, 30)
--trigger.action.outSound("Quest Snare 3.wav") --trigger.action.outSound("Quest Snare 3.wav")
if aZone.autoRemove then if aZone.autoRemove then
-- maybe schedule this in a few seconds? -- schedule this for in a few seconds?
object:destroy() local args = {}
args.theObject = object
args.theZone = aZone
timer.scheduleFunction(cfxCargoReceiver.removeCargo, args, timer.getTime() + aZone.removeDelay)
--object:destroy()
end end
end end
end end
@ -177,11 +203,16 @@ function cfxCargoReceiver.update()
-- new we see if any of these are close to a delivery zone -- new we see if any of these are close to a delivery zone
for idx, aCargo in pairs(liftedCargos) do for idx, aCargo in pairs(liftedCargos) do
local thePoint = aCargo:getPoint() local thePoint = aCargo:getPoint()
local receiver, delta = cfxZones.getClosestZone( local receiver = cfxZones.getClosestZone(
thePoint, thePoint,
cfxCargoReceiver.receiverZones -- must be indexed by name cfxCargoReceiver.receiverZones -- must be indexed by name
) )
-- we now check if we are in 'speaking range' and receiver can talk -- we now check if we are in 'speaking range' and receiver can talk
-- modify delta by distance to boundary, not
-- center
local delta = dcsCommon.distFlat(thePoint, cfxZones.getPoint(receiver))
delta = delta - receiver.radius
if (receiver.silent == false) and if (receiver.silent == false) and
(delta < cfxCargoReceiver.maxDirectionRange) then (delta < cfxCargoReceiver.maxDirectionRange) then
-- this cargo can be talked down. -- this cargo can be talked down.
@ -195,7 +226,7 @@ function cfxCargoReceiver.update()
local theUnit = info.unit local theUnit = info.unit
if theUnit:isExist() then if theUnit:isExist() then
local uPoint = theUnit:getPoint() local uPoint = theUnit:getPoint()
local currDelta = dcsCommon.dist(thePoint, uPoint) local currDelta = dcsCommon.distFlat(thePoint, uPoint)
if currDelta < minDelta then if currDelta < minDelta then
minDelta = currDelta minDelta = currDelta
closestUnit = theUnit closestUnit = theUnit
@ -217,7 +248,7 @@ function cfxCargoReceiver.update()
receiver.point, receiver.point,
thePoint, thePoint,
ownHeading) .. " o'clock" ownHeading) .. " o'clock"
message = receiver.name .. " is " .. math.floor(delta) .. "m at your " .. oclock message = receiver.name .. " (r=" .. receiver.radius .. "m) is " .. math.floor(delta) .. "m at your " .. oclock
end end
-- add agl -- add agl
local agl = dcsCommon.getUnitAGL(aCargo) local agl = dcsCommon.getUnitAGL(aCargo)

View File

@ -1,5 +1,6 @@
cfxMX = {} cfxMX = {}
cfxMX.version = "1.1.0" cfxMX.version = "1.2.0"
cfxMX.verbose = false
--[[-- --[[--
Mission data decoder. Access to ME-built mission structures Mission data decoder. Access to ME-built mission structures
@ -12,6 +13,10 @@ cfxMX.version = "1.1.0"
- on start up collects a cross reference table of all - on start up collects a cross reference table of all
original group id original group id
- add linkUnit for statics - add linkUnit for statics
1.2.0 - added group name reference table
- added group type reference
- added references for allFixed, allHelo, allGround, allSea, allStatic
--]]-- --]]--
@ -19,6 +24,11 @@ cfxMX.groupNamesByID = {}
cfxMX.groupIDbyName = {} cfxMX.groupIDbyName = {}
cfxMX.groupDataByName = {} cfxMX.groupDataByName = {}
cfxMX.allFixedByName = {}
cfxMX.allHeloByName = {}
cfxMX.allGroundByName = {}
cfxMX.allSeaByName = {}
cfxMX.allStaticByName ={}
function cfxMX.getGroupFromDCSbyName(aName, fetchOriginal) function cfxMX.getGroupFromDCSbyName(aName, fetchOriginal)
if not fetchOriginal then fetchOriginal = false end if not fetchOriginal then fetchOriginal = false end
@ -154,7 +164,7 @@ function cfxMX.getStaticFromDCSbyName(aName, fetchOriginal)
return nil, "<none>", "<none>", "<no group name>" return nil, "<none>", "<none>", "<no group name>"
end end
function cfxMX.createCrossReference() function cfxMX.createCrossReferences()
for coa_name_miz, coa_data in pairs(env.mission.coalition) do -- iterate all coalitions for coa_name_miz, coa_data in pairs(env.mission.coalition) do -- iterate all coalitions
local coa_name = coa_name_miz local coa_name = coa_name_miz
if string.lower(coa_name_miz) == 'neutrals' then -- remove 's' at neutralS if string.lower(coa_name_miz) == 'neutrals' then -- remove 's' at neutralS
@ -178,7 +188,7 @@ function cfxMX.createCrossReference()
obj_type_name == "ship" or obj_type_name == "ship" or
obj_type_name == "plane" or obj_type_name == "plane" or
obj_type_name == "vehicle" or obj_type_name == "vehicle" or
obj_type_name == "static" obj_type_name == "static" -- what about "cargo"?
then -- (so it's not id or name) then -- (so it's not id or name)
local category = obj_type_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! 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!
@ -188,6 +198,21 @@ function cfxMX.createCrossReference()
cfxMX.groupNamesByID[aID] = aName cfxMX.groupNamesByID[aID] = aName
cfxMX.groupIDbyName[aName] = aID cfxMX.groupIDbyName[aName] = aID
cfxMX.groupDataByName[aName] = group_data cfxMX.groupDataByName[aName] = group_data
-- now make the type-specific xrefs
if obj_type_name == "helicopter" then
cfxMX.allHeloByName[aName] = group_data
elseif obj_type_name == "ship" then
cfxMX.allSeaByName[aName] = group_data
elseif obj_type_name == "plane" then
cfxMX.allFixedByName[aName] = group_data
elseif obj_type_name == "vehicle" then
cfxMX.allGroundByName[aName] = group_data
elseif obj_type_name == "static" then
cfxMX.allStaticByName[aName] = group_data
else
-- should be impossible, but still
trigger.action.outText("+++MX: <" .. obj_type_name .. "> unknown type for <" .. aName .. ">", 30)
end
end end
end --if has category data end --if has category data
end --if plane, helo etc... category end --if plane, helo etc... category
@ -212,8 +237,10 @@ function cfxMX.catText2ID(inText)
end end
function cfxMX.start() function cfxMX.start()
cfxMX.createCrossReference() cfxMX.createCrossReferences()
trigger.action.outText("cfxMX: "..#cfxMX.groupNamesByID .. " groups processed successfully", 30) if cfxMX.verbose then
trigger.action.outText("cfxMX: "..#cfxMX.groupNamesByID .. " groups processed successfully", 30)
end
end end
-- start -- start

View File

@ -1,5 +1,5 @@
cfxObjectSpawnZones = {} cfxObjectSpawnZones = {}
cfxObjectSpawnZones.version = "1.2.1" cfxObjectSpawnZones.version = "1.3.0"
cfxObjectSpawnZones.requiredLibs = { cfxObjectSpawnZones.requiredLibs = {
"dcsCommon", -- common is of course needed for everything "dcsCommon", -- common is of course needed for everything
-- pretty stupid to check for this since we -- pretty stupid to check for this since we
@ -26,7 +26,10 @@ cfxObjectSpawnZones.verbose = false
-- 1.1.5 - spawn?, spawnObjects? synonyms -- 1.1.5 - spawn?, spawnObjects? synonyms
-- 1.2.0 - DML flag upgrade -- 1.2.0 - DML flag upgrade
-- 1.2.1 - config zone -- 1.2.1 - config zone
-- - autoLink bug (zone instead of spaneer accessed) -- - 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
-- respawn currently happens after theSpawns is deleted and cooldown seconds have passed -- respawn currently happens after theSpawns is deleted and cooldown seconds have passed
cfxObjectSpawnZones.allSpawners = {} cfxObjectSpawnZones.allSpawners = {}
@ -58,13 +61,9 @@ function cfxObjectSpawnZones.createSpawner(inZone)
-- connect with ME if a trigger flag is given -- connect with ME if a trigger flag is given
if cfxZones.hasProperty(inZone, "f?") then if cfxZones.hasProperty(inZone, "f?") then
theSpawner.triggerFlag = cfxZones.getStringFromZoneProperty(inZone, "f?", "none") theSpawner.triggerFlag = cfxZones.getStringFromZoneProperty(inZone, "f?", "none")
end elseif cfxZones.hasProperty(inZone, "spawn?") then
if cfxZones.hasProperty(inZone, "spawn?") then
theSpawner.triggerFlag = cfxZones.getStringFromZoneProperty(inZone, "spawn?", "none") theSpawner.triggerFlag = cfxZones.getStringFromZoneProperty(inZone, "spawn?", "none")
end elseif cfxZones.hasProperty(inZone, "spawnObjects?") then
if cfxZones.hasProperty(inZone, "spawnObjects?") then
theSpawner.triggerFlag = cfxZones.getStringFromZoneProperty(inZone, "spawnObjects?", "none") theSpawner.triggerFlag = cfxZones.getStringFromZoneProperty(inZone, "spawnObjects?", "none")
end end
@ -116,6 +115,11 @@ function cfxObjectSpawnZones.createSpawner(inZone)
theSpawner.requestable = cfxZones.getBoolFromZoneProperty(inZone, "requestable", false) theSpawner.requestable = cfxZones.getBoolFromZoneProperty(inZone, "requestable", false)
if theSpawner.requestable then theSpawner.paused = true end if theSpawner.requestable then theSpawner.paused = true end
-- see if the spawn can be made brittle/delicte
if cfxZones.hasProperty(inZone, "useDelicates") then
theSpawner.delicateName = cfxZones.getStringFromZoneProperty(inZone, "useDelicates", "<none>")
end
-- see if it is linked to a ship to set realtive orig headiong -- see if it is linked to a ship to set realtive orig headiong
if inZone.linkedUnit then if inZone.linkedUnit then
@ -258,6 +262,16 @@ function cfxObjectSpawnZones.spawnObjectNTimes(aSpawner, theType, n, container)
end end
end end
if aSpawner.delicateName and delicates then
-- pass this object to the delicate zone mentioned
local theDeli = delicates.getDelicatesByName(aSpawner.delicateName)
if theDeli then
delicates.addStaticObjectToInventoryForZone(theDeli, theObject)
else
trigger.action.outText("+++oSpwn: spawner <" .. aZone.name .. "> can't find delicates <" .. aSpawner.delicateName .. ">", 30)
end
end
return return
end end
@ -302,6 +316,17 @@ function cfxObjectSpawnZones.spawnObjectNTimes(aSpawner, theType, n, container)
cfxCargoManager.addCargo(theObject) cfxCargoManager.addCargo(theObject)
end end
end end
if aSpawner.delicateName and delicates then
-- pass this object to the delicate zone mentioned
local theDeli = delicates.getDelicatesByName(aSpawner.delicateName)
if theDeli then
delicates.addStaticObjectToInventoryForZone(theDeli, theObject)
else
trigger.action.outText("+++oSpwn: spawner <" .. aZone.name .. "> can't find delicates <" .. aSpawner.delicateName .. ">", 30)
end
end
-- update rotation -- update rotation
currDegree = currDegree + degreeIncrement currDegree = currDegree + degreeIncrement
end end

View File

@ -1,5 +1,5 @@
cfxZones = {} cfxZones = {}
cfxZones.version = "2.8.3" cfxZones.version = "2.8.4"
-- cf/x zone management module -- cf/x zone management module
-- reads dcs zones and makes them accessible and mutable -- reads dcs zones and makes them accessible and mutable
@ -88,6 +88,7 @@ cfxZones.version = "2.8.3"
- 2.8.3 - new verifyMethod() - 2.8.3 - new verifyMethod()
- changed extractPropertyFromDCS() to also match attributes with blanks like "the Attr" to "theAttr" - changed extractPropertyFromDCS() to also match attributes with blanks like "the Attr" to "theAttr"
- new expandFlagName() - new expandFlagName()
- 2.8.4 - fixed bug in setFlagValue()
--]]-- --]]--
cfxZones.verbose = false cfxZones.verbose = false
@ -867,11 +868,12 @@ end
-- get closest zone returns the zone that is closest to point -- get closest zone returns the zone that is closest to point
function cfxZones.getClosestZone(point, theZones) function cfxZones.getClosestZone(point, theZones)
if not theZones then theZones = cfxZones.zones end if not theZones then theZones = cfxZones.zones end
local lPoint = {x=point.x, y=0, z=point.z}
local currDelta = math.huge local currDelta = math.huge
local closestZone = nil local closestZone = nil
for zName, zData in pairs(theZones) do for zName, zData in pairs(theZones) do
local zPoint = cfxZones.getPoint(zData) local zPoint = cfxZones.getPoint(zData)
local delta = dcsCommon.dist(point, zPoint) local delta = dcsCommon.dist(lPoint, zPoint) -- emulate flag compare
if (delta < currDelta) then if (delta < currDelta) then
currDelta = delta currDelta = delta
closestZone = zData closestZone = zData
@ -1276,7 +1278,7 @@ end
function cfxZones.setFlagValue(theFlag, theValue, theZone) function cfxZones.setFlagValue(theFlag, theValue, theZone)
local zoneName = "<dummy>" local zoneName = "<dummy>"
if not theZone then if not theZone then
trigger.action.outText("+++Zne: no zone on setFlagValue") trigger.action.outText("+++Zne: no zone on setFlagValue", 30) -- mod me for detector
else else
zoneName = theZone.name -- for flag wildcards zoneName = theZone.name -- for flag wildcards
end end

View File

@ -1,5 +1,5 @@
dcsCommon = {} dcsCommon = {}
dcsCommon.version = "2.6.6" dcsCommon.version = "2.6.8"
--[[-- VERSION HISTORY --[[-- VERSION HISTORY
2.2.6 - compassPositionOfARelativeToB 2.2.6 - compassPositionOfARelativeToB
- clockPositionOfARelativeToB - clockPositionOfARelativeToB
@ -82,7 +82,9 @@ dcsCommon.version = "2.6.6"
- new stringRemainsStartingWith() - new stringRemainsStartingWith()
- new stripLF() - new stripLF()
- new removeBlanks() - new removeBlanks()
2.6.7 - new menu2text()
2.6.8 - new getMissionName()
- new flagArrayFromString()
--]]-- --]]--
-- dcsCommon is a library of common lua functions -- dcsCommon is a library of common lua functions
@ -1882,6 +1884,21 @@ dcsCommon.version = "2.6.6"
return catNum return catNum
end end
function dcsCommon.menu2text(inMenu)
if not inMenu then return "<nil>" end
local s = ""
for n, v in pairs(inMenu) do
if type(v) == "string" then
if s == "" then s = "[" .. v .. "]" else
s = s .. " | [" .. type(v) .. "]" end
else
if s == "" then s = "[<" .. type(v) .. ">]" else
s = s .. " | [<" .. type(v) .. ">]" end
end
end
return s
end
-- recursively show the contents of a variable -- recursively show the contents of a variable
function dcsCommon.dumpVar(key, value, prefix, inrecursion) function dcsCommon.dumpVar(key, value, prefix, inrecursion)
if not inrecursion then if not inrecursion then
@ -2361,6 +2378,68 @@ function dcsCommon.latLon2Text(lat, lon)
return lat, lon return lat, lon
end end
-- get mission name. If mission file name without ".miz"
function dcsCommon.getMissionName()
local mn = net.dostring_in("gui", "return DCS.getMissionName()")
return mn
end
function dcsCommon.flagArrayFromString(inString, verbose)
if not verbose then verbose = false end
if verbose then
trigger.action.outText("+++flagArray: processing <" .. inString .. ">", 30)
end
if string.len(inString) < 1 then
trigger.action.outText("+++flagArray: empty flags", 30)
return {}
end
local flags = {}
local rawElements = dcsCommon.splitString(inString, ",")
-- go over all elements
for idx, anElement in pairs(rawElements) do
if dcsCommon.stringStartsWithDigit(anElement) and dcsCommon.containsString(anElement, "-") then
-- interpret this as a range
local theRange = dcsCommon.splitString(anElement, "-")
local lowerBound = theRange[1]
lowerBound = tonumber(lowerBound)
local upperBound = theRange[2]
upperBound = tonumber(upperBound)
if lowerBound and upperBound then
-- swap if wrong order
if lowerBound > upperBound then
local temp = upperBound
upperBound = lowerBound
lowerBound = temp
end
-- now add add numbers to flags
for f=lowerBound, upperBound do
table.insert(flags, f)
end
else
-- bounds illegal
trigger.action.outText("+++flagArray: ignored range <" .. anElement .. "> (range)", 30)
end
else
-- single number
f = dcsCommon.trim(anElement) -- DML flag upgrade: accept strings tonumber(anElement)
if f then
table.insert(flags, f)
else
trigger.action.outText("+++flagArray: ignored element <" .. anElement .. "> (single)", 30)
end
end
end
if verbose then
trigger.action.outText("+++flagArray: <" .. #flags .. "> flags total", 30)
end
return flags
end
-- --
-- --
-- INIT -- INIT

View File

@ -1,5 +1,5 @@
delicates = {} delicates = {}
delicates.version = "1.0.0" delicates.version = "1.1.0"
delicates.verbose = false delicates.verbose = false
delicates.ups = 1 delicates.ups = 1
delicates.requiredLibs = { delicates.requiredLibs = {
@ -12,6 +12,11 @@ delicates.inventory = {}
--[[-- --[[--
Version History Version History
1.0.0 - initial version 1.0.0 - initial version
1.1.0 - better synonym handling for f! and out!
- addStaticObjectInventoryForZone
- blowAll?
- safetyMargin - safety margin. defaults to 10%
--]]-- --]]--
function delicates.adddDelicates(theZone) function delicates.adddDelicates(theZone)
@ -68,7 +73,7 @@ function delicates.makeZoneInventory(theZone)
for idy, anObject in pairs(collector) do for idy, anObject in pairs(collector) do
local oName = anObject:getName() local oName = anObject:getName()
if type(oName) == 'number' then oName = tostring(oName) end if type(oName) == 'number' then oName = tostring(oName) end
local oLife = anObject:getLife() local oLife = anObject:getLife() - anObject:getLife() * theZone.safetyMargin
if theZone.verbose or delicates.verbose then if theZone.verbose or delicates.verbose then
trigger.action.outText("+++deli: cat=".. aCat .. ":<" .. oName .. "> Life=" .. oLife, 30) trigger.action.outText("+++deli: cat=".. aCat .. ":<" .. oName .. "> Life=" .. oLife, 30)
end end
@ -91,25 +96,47 @@ function delicates.makeZoneInventory(theZone)
end end
end end
function delicates.addStaticObjectToInventoryForZone(theZone, theStatic)
if not theZone then return end
if not theStatic then return end
local desc = {}
desc.cat = theStatic:getCategory()
desc.oLife = theStatic:getLife() - theStatic:getLife() * theZone.safetyMargin
if desc.oLife < 0 then desc.oLife = 0 end
desc.theZone = theZone
desc.oName = theStatic:getName()
delicates.inventory[desc.oName] = desc
if theZone.verbose or delicates.verbose then
trigger.action.outText("+++deli: added static <" .. desc.oName .. "> to <" .. theZone.name .. "> with minimal life = <" .. desc.oLife .. "/" .. theStatic:getLife() .. "> = safety margin of " .. theZone.safetyMargin * 100 .. "%", 30)
end
end
function delicates.createDelicatesWithZone(theZone) function delicates.createDelicatesWithZone(theZone)
theZone.power = cfxZones.getNumberFromZoneProperty(theZone, "power", 10) theZone.power = cfxZones.getNumberFromZoneProperty(theZone, "power", 10)
if cfxZones.hasProperty(theZone, "delicatesHit!") then if cfxZones.hasProperty(theZone, "delicatesHit!") then
theZone.delicateHit = cfxZones.getStringFromZoneProperty(theZone, "delicatesHit!", "*<none>") theZone.delicateHit = cfxZones.getStringFromZoneProperty(theZone, "delicatesHit!", "*<none>")
end elseif cfxZones.hasProperty(theZone, "f!") then
if cfxZones.hasProperty(theZone, "f!") then
theZone.delicateHit = cfxZones.getStringFromZoneProperty(theZone, "f!", "*<none>") theZone.delicateHit = cfxZones.getStringFromZoneProperty(theZone, "f!", "*<none>")
end elseif cfxZones.hasProperty(theZone, "out!") then
if cfxZones.hasProperty(theZone, "out!") then
theZone.delicateHit = cfxZones.getStringFromZoneProperty(theZone, "out!", "*<none>") theZone.delicateHit = cfxZones.getStringFromZoneProperty(theZone, "out!", "*<none>")
end end
-- safety margin
theZone.safetyMargin = cfxZones.getNumberFromZoneProperty(theZone, "safetyMargin", 0)
-- DML Method -- DML Method
theZone.delicateHitMethod = cfxZones.getStringFromZoneProperty(theZone, "method", "inc") theZone.delicateHitMethod = cfxZones.getStringFromZoneProperty(theZone, "method", "inc")
if cfxZones.hasProperty(theZone, "delicateMethod") then if cfxZones.hasProperty(theZone, "delicateMethod") then
theZone.delicateHitMethod = cfxZones.getStringFromZoneProperty(theZone, "delicatesMethod", "inc") theZone.delicateHitMethod = cfxZones.getStringFromZoneProperty(theZone, "delicatesMethod", "inc")
end end
theZone.delicateTriggerMethod = cfxZones.getStringFromZoneProperty(theZone, "thriggerMethod", "change")
if cfxZones.hasProperty(theZone, "delicateTriggerMethod") then
theZone.delicateTriggerMethod = cfxZones.getStringFromZoneProperty(theZone, "delicatesMethod", "change")
end
theZone.delicateRemove = cfxZones.getBoolFromZoneProperty(theZone, "remove", true) theZone.delicateRemove = cfxZones.getBoolFromZoneProperty(theZone, "remove", true)
@ -117,6 +144,11 @@ function delicates.createDelicatesWithZone(theZone)
-- may want to filter by objects, can be passed in delicates -- may want to filter by objects, can be passed in delicates
delicates.makeZoneInventory(theZone) delicates.makeZoneInventory(theZone)
if cfxZones.hasProperty(theZone, "blowAll?") then
theZone.blowAll = cfxZones.getStringFromZoneProperty(theZone, "blowAll?", "*<none>")
theZone.lastBlowAll = cfxZones.getFlagValue(theZone.blowAll, theZone)
end
if delicates.verbose or theZone.verbose then if delicates.verbose or theZone.verbose then
trigger.action.outText("+++deli: new delicates zone <".. theZone.name ..">", 30) trigger.action.outText("+++deli: new delicates zone <".. theZone.name ..">", 30)
end end
@ -185,14 +217,42 @@ function delicates:onEvent(theEvent)
local oName = theObj:getName() local oName = theObj:getName()
local desc = delicates.inventory[oName] local desc = delicates.inventory[oName]
if desc then if desc then
-- trigger.action.outText("+++deli: REGISTERED HIT -- removing!", 30) -- see if damage exceeds maximum
delicates.blowUpObject(desc) local cLife = theObj:getLife()
-- remove it from further searches if cLife < desc.oLife then
delicates.inventory[oName] = nil if desc.theZone.verbose or delicates.verbose then
trigger.action.outText("+++deli: BRITTLE TRIGGER: life <" .. cLife .. "> below safety margin <" .. oDesc.oLife .. ">", 30)
end
delicates.blowUpObject(desc)
-- remove it from further searches
delicates.inventory[oName] = nil
else
if desc.theZone.verbose or delicates.verbose then
trigger.action.outText("+++deli: CLOSE CALL, but life <" .. cLife .. "> within safety margin <" .. oDesc.oLife .. ">", 30)
end
end
end end
-- trigger.action.outText("+++deli: we hit " .. oName, 30) end
--
-- blow entire zone
--
function delicates.blowZone(theZone)
if not theZone then return end
local zName = theZone.name
local newInventory = {}
local delay = 0.7
for oName, oDesc in pairs (delicates.inventory) do
if oDesc.theZone.name == zName then
delicates.blowUpObject(oDesc, delay)
delay = delay + 0.2 -- stagger explosions
else
newInventory[oName] = oDesc
end
end
delicates.inventory = newInventory
end end
-- --
@ -228,12 +288,12 @@ function delicates.update()
if theObj then if theObj then
local cLife = theObj:getLife() local cLife = theObj:getLife()
if cLife >= oDesc.oLife then if cLife >= oDesc.oLife then
-- transfer to next iter -- all well, transfer to next iter
newInventory[oName] = oDesc newInventory[oName] = oDesc
else else
-- blow stuff up -- health beneath min. blow stuff up
if oDesc.theZone.verbose or delicates.verbose then if oDesc.theZone.verbose or delicates.verbose then
trigger.action.outText(oName .. " was hit, will blow up, new health is at " .. oDesc.oLife .. ".", 30) trigger.action.outText(oName .. " was hit, will blow up, current health is <" .. cLife .. ">, min health was " .. oDesc.oLife .. ".", 30)
end end
delicates.blowUpObject(oDesc) delicates.blowUpObject(oDesc)
end end
@ -245,6 +305,13 @@ function delicates.update()
end end
end end
delicates.inventory = newInventory delicates.inventory = newInventory
-- now scan all zones for signals
for idx, theZone in pairs(delicates.theDelicates) do
if theZone.blowAll and cfxZones.testZoneFlag(theZone, theZone.blowAll, theZone.delicateTriggerMethod, "lastBlowAll") then
delicates.blowZone(theZone)
end
end
end end
-- --

View File

@ -566,5 +566,5 @@ end
To do To do
- reset? flag: will reset all to MX locationS - reset? flag: will reset all to MX locationS
- add a zone's follow ability to impostors by allowing linkedUnit to work with impostors - add a zone's follow ability to impostors by allowing linkedUnit to work with impostors
- impostor on idle option. when task of group goes to idle, the group turns into impostors
--]]-- --]]--

584
modules/persistence.lua Normal file
View File

@ -0,0 +1,584 @@
persistence = {}
persistence.version = "1.0.0"
persistence.ups = 1 -- once every 1 seconds
persistence.verbose = false
persistence.active = false
persistence.saveFileName = nil -- "mission data.txt"
persistence.sharedDir = nil -- not yet implemented
persistence.missionDir = nil -- set at start
persistence.saveDir = nil -- set at start
persistence.missionData = {} -- loaded from file
persistence.requiredLibs = {
"dcsCommon", -- always
"cfxZones", -- Zones, of course
}
--[[--
Version History
1.0.0 - initial version
PROVIDES LOAD/SAVE ABILITY TO MODULES
PROVIDES STANDALONE/HOSTED SERVER COMPATIOBILITY
--]]--
-- in order to work, Host must desanitize lfs and io
-- only works when run as server
--
-- flags to save. can be added to by saveFlags attribute
--
persistence.flagsToSave = {} -- simple table
persistence.callbacks = {} -- cbblocks, dictionary
--
-- modules register here
--
function persistence.registerModule(name, callbacks)
-- callbacks is a table with the following entries
-- callbacks.persistData - method that returns a table
-- note that name is also what the data is saved under
-- and must be the one given when you retrieve it later
persistence.callbacks[name] = callbacks
if persistence.verbose then
trigger.action.outText("+++persistence: module <" .. name .. "> registred itself", 30)
end
end
function persistence.registerFlagsToSave(flagNames, theZone)
-- name can be single flag name or anything that
-- a zone definition has to offer, including local
-- flags.
-- flags can be passed like this: "a, 4-19, 99, kills, *lcl"
-- if you pass a local flag, you must pass the zone
-- or "persisTEMP" will be used
if not theZone then theZone = cfxZones.createSimpleZone("persisTEMP") end
local newFlags = dcsCommon.flagArrayFromString(flagNames, persistence.verbose)
-- mow process all new flags and add them to the list of flags
-- to save
for idx, flagName in pairs(newFlags) do
if dcsCommon.stringStartsWith(flagName, "*") then
flagName = theZone.name .. flagName
end
table.insert(persistence.flagsToSave, flagName)
end
end
--
-- registered modules call this to get their data
--
function persistence.getSavedDataForModule(name)
if not persistence.active then return nil end
if not persistence.hasData then return nil end
if not persistence.missionData then return end
return persistence.missionData[name] -- simply get the modules data block
end
--
-- Shared Data API
--
function persistence.getSharedDataFor(name, item) -- not yet finalized
end
function persistence.putSharedDataFor(data, name, item) -- not yet finalized
end
--
-- helper meths
--
function persistence.hasFile(path) --check if file exists at path
-- will also return true for a directory, follow up with isDir
local attr = lfs.attributes(path)
if attr then
return true, attr.mode
end
if persistence.verbose then
trigger.action.outText("isFile: attributes not found for <" .. path .. ">", 30)
end
return false, "<err>"
end
function persistence.isDir(path) -- check if path is a directory
local success, mode = persistence.hasFile(path)
if success then
success = (mode == "directory")
end
return success
end
--
-- Main save meths
--
function persistence.saveText(theString, fileName, shared, append)
if not persistence.active then return false end
if not fileName then return false end
if not shared then shared = flase end
if not theString then theString = "" end
local path = persistence.missionDir .. fileName
if shared then
-- we would now change the path
trigger.action.outText("+++persistence: NYI: shared", 30)
return
end
local theFile = nil
if append then
theFile = io.open(path, "a")
else
theFile = io.open(path, "w")
end
if not theFile then
return false
end
theFile:write(theString)
theFile:close()
return true
end
function persistence.saveTable(theTable, fileName, shared, append)
if not persistence.active then return false end
if not fileName then return false end
if not theTable then return false end
if not shared then shared = false end
local theString = net.lua2json(theTable)
if not theString then theString = "" end
local path = persistence.missionDir .. fileName
if shared then
-- we would now change the path
trigger.action.outText("+++persistence: NYI: shared", 30)
return
end
local theFile = nil
if append then
theFile = io.open(path, "a")
else
theFile = io.open(path, "w")
end
if not theFile then
return false
end
theFile:write(theString)
theFile:close()
return true
end
function persistence.loadText(fileName) -- load file as text
if not persistence.active then return nil end
if not fileName then return nil end
local path = persistence.missionDir .. fileName
local theFile = io.open(path, "r")
if not theFile then return nil end
local t = theFile:read("*a")
theFile:close()
return t
end
function persistence.loadTable(fileName) -- load file as table
if not persistence.active then return nil end
if not fileName then return nil end
local t = persistence.loadText(fileName)
if not t then return nil end
local tab = net.json2lua(t)
return tab
end
--
-- Data Load on Start
--
function persistence.initFlagsFromData(theFlags)
-- assumes that theFlags is a dictionary containing
-- flag names
local flagLog = ""
local flagCount = 0
for flagName, value in pairs(theFlags) do
local val = tonumber(value) -- ensure number
if not val then val = 0 end
trigger.action.setUserFlag(flagName, val)
if flagLog ~= "" then
flagLog = flagLog .. ", " .. flagName .. "=" .. val
else
flagLog = flagName .. "=" .. val
end
flagCount = flagCount + 1
end
if persistence.verbose and flagCount > 0 then
trigger.action.outText("+++persistence: loaded " .. flagCount .. " flags from storage:\n" .. flagLog .. "", 30)
elseif persistence.verbose then
trigger.action.outText("+++persistence: no flags loaded, commencing mission data load", 30)
end
end
function persistence.missionStartDataLoad()
-- check one: see if we have mission data
local theData = persistence.loadTable(persistence.saveFileName)
if not theData then
if persistence.verbose then
trigger.action.outText("+++persistence: no saved data, fresh start.", 30)
end
return
end -- there was no data to load
if theData["freshMaker"] then
if persistence.verbose then
trigger.action.outText("+++persistence: detected fresh start.", 30)
end
return
end
-- when we get here, we got at least some data. check it
if theData["versionID"] or persistence.versionID then
local vid = theData.versionID -- note: either may be nil!
if vid ~= persistence.versionID then
-- we pretend load never happened.
-- simply return
if persistence.verbose then
local curvid = persistence.versionID
if not curvid then curvid = "<NIL>" end
if not vid then vid = "<NIL>" end
trigger.action.outText("+++persistence: version mismatch\n(saved = <" .. vid .. "> vs current = <" .. curvid .. ">) - fresh start.", 30)
end
return
end
end
-- we have valid data, and modules, after signing up
-- can init from by data
persistence.missionData = theData
persistence.hasData = true
-- init my flags from last save
local theFlags = theData["persistence.flagData"]
if theFlags then
persistence.initFlagsFromData(theFlags)
end
-- we are done for now. modules check in
-- after persistence and load their own data
-- when they detect that there is data to load
if persistence.verbose then
trigger.action.outText("+++persistence: basic import complete.", 30)
end
end
--
-- MAIN DATA WRITE
--
function persistence.collectFlagData()
local flagData = {}
for idx, flagName in pairs (persistence.flagsToSave) do
local theNum = trigger.misc.getUserFlag(flagName)
flagData[flagName] = theNum
end
return flagData
end
function persistence.saveMissionData()
local myData = {}
-- first, handle versionID and freshMaker
if persistence.freshMaker then
myData["freshMaker"] = true
end
if persistence.versionID then
myData["versionID"] = persistence.versionID
end
-- now handle flags
myData["persistence.flagData"] = persistence.collectFlagData()
-- now handle all other modules
for moduleName, callbacks in pairs(persistence.callbacks) do
local moduleData = callbacks.persistData()
if moduleData then
myData[moduleName] = moduleData
if persistence.verbose then
trigger.action.outText("+++persistence: gathered data from <" .. moduleName .. ">", 30)
end
end
end
-- now save data to file
persistence.saveTable(myData, persistence.saveFileName)
end
--
-- UPDATE
--
function persistence.doSaveMission()
-- main save entry, also from API
if persistence.verbose then
trigger.action.outText("+++persistence: starting save", 30)
end
if persistence.active then
persistence.saveMissionData()
else
if persistence.verbose then
trigger.action.outText("+++persistence: not actice. skipping save", 30)
end
return
end
if persistence.verbose then
trigger.action.outText("+++persistence: mission saved", 30)
end
end
function persistence.noteCleanRestart()
persistence.freshMaker = true
persistence.doSaveMission()
trigger.action.outText("\n\nYou can re-start the mission for a fresh start.\n\n",30)
end
function persistence.update()
-- call me in a second to poll triggers
timer.scheduleFunction(persistence.update, {}, timer.getTime() + 1/persistence.ups)
-- check my trigger flag
if persistence.saveMission and cfxZones.testZoneFlag(persistence, persistence.saveMission, "change", "lastSaveMission") then
persistence.doSaveMission()
end
if persistence.cleanRestart and cfxZones.testZoneFlag(persistence, persistence.cleanRestart, "change", "lastCleanRestart") then
persistence.noteCleanRestart()
end
-- check my timer
if persistence.saveTime and persistence.saveTime < timer.getTime() then
persistence.doSaveMission()
-- start next cycle
persistence.saveTime = persistence.saveInterval * 60 + timer.getTime()
end
end
--
-- config & start
--
function persistence.collectFlagsFromZone(theZone)
local theFlags = cfxZones.getStringFromZoneProperty(theZone, "saveFlags", "*dummy")
persistence.registerFlagsToSave(theFlags, theZone)
end
function persistence.readConfigZone()
local theZone = cfxZones.getZoneByName("persistenceConfig")
local hasConfig = true
if not theZone then
hasConfig = false
theZone = cfxZones.createSimpleZone("persistenceConfig")
end
-- serverDir is the path from the server save directory, usually "Missions/".
-- will be added to lfs.writedir().
persistence.serverDir = cfxZones.getStringFromZoneProperty(theZone, "serverDir", "Missions\\")
if hasConfig then
if cfxZones.hasProperty(theZone, "saveDir") then
persistence.saveDir = cfxZones.getStringFromZoneProperty(theZone, "saveDir", "")
else
-- local missname = net.dostring_in("gui", "return DCS.getMissionName()") .. " (data)"
persistence.saveDir = dcsCommon.getMissionName() .. " (data)"
end
else
persistence.saveDir = "" -- save dir is to main mission
-- so that when no config is present (standalone debugger)
-- this will not cause a separate save folder
end
if persistence.saveDir == "" and persistence.verbose then
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")
end
if cfxZones.hasProperty(theZone, "versionID") then
persistence.versionID = cfxZones.getStringFromZoneProperty(theZone, "versionID", "") -- to check for full restart
end
persistence.saveInterval = cfxZones.getNumberFromZoneProperty(theZone, "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)
end
if cfxZones.hasProperty(theZone, "saveMission?") then
persistence.saveMission = cfxZones.getStringFromZoneProperty(theZone, "saveMission?", "*<none>")
persistence.lastSaveMission = cfxZones.getFlagValue(persistence.saveMission, theZone)
end
persistence.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false)
if persistence.verbose then
trigger.action.outText("+++persistence: read config", 30)
end
end
function persistence.start()
-- lib check
if not dcsCommon.libCheck then
trigger.action.outText("persistence requires dcsCommon", 30)
return false
end
if not dcsCommon.libCheck("persistence", persistence.requiredLibs) then
return false
end
-- read config
persistence.saveFileName = dcsCommon.getMissionName() .. " Data.txt"
persistence.readConfigZone()
-- let's see it lfs and io are online
persistence.active = false
if not _G["lfs"] then
if persistence.verbose then
trigger.action.outText("+++persistence requires 'lfs'", 30)
return false
end
end
if not _G["io"] then
if persistence.verbose then
trigger.action.outText("+++persistence requires 'io'", 30)
return false
end
end
local mainDir = lfs.writedir() .. 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
-- we first try to access server's main mission directory, called "mainDir" which is usually <writeDir>/Missions/>
if persistence.isDir(mainDir) then
if persistence.verbose then
trigger.action.outText("persistence: main dir is <" .. mainDir .. ">", 30)
end
else
if persistence.verbose then
trigger.action.outText("+++persistence: Main directory <" .. mainDir .. "> not found or not a directory", 30)
end
return false
end
persistence.mainDir = mainDir
local missionDir = mainDir .. persistence.saveDir
if not dcsCommon.stringEndsWith(missionDir, "\\") then
missionDir = missionDir .. "\\"
end
-- check if mission dir exists already
local success, mode = persistence.hasFile(missionDir)
if success and mode == "directory" then
-- has been allocated, and is dir
if persistence.verbose then
trigger.action.outText("+++persistence: saving mission data to <" .. missionDir .. ">", 30)
end
elseif success then
if persistence.verbose then
trigger.action.outText("+++persistence: <" .. missionDir .. "> is not a directory", 30)
end
return false
else
-- does not exist, try to allocate it
if persistence.verbose then
trigger.action.outText("+++persistence: will now create <" .. missionDir .. ">", 30)
end
local ok, mkErr = lfs.mkdir(missionDir)
if not ok then
if persistence.verbose then
trigger.action.outText("+++persistence: unable to create <" .. missionDir .. ">: <" .. mkErr .. ">", 30)
end
return false
end
if persistence.verbose then
trigger.action.outText("+++persistence: created <" .. missionDir .. "> successfully, will save mission data here", 30)
end
end
persistence.missionDir = missionDir
persistence.active = true -- we can load and save data
persistence.hasData = false -- we do not have save data
-- from here on we can read and write files in the missionDir
-- read persistence attributes from all zones
local attrZones = cfxZones.getZonesWithAttributeNamed("saveFlags")
for k, aZone in pairs(attrZones) do
persistence.collectFlagsFromZone(aZone) -- process attributes
-- we do not retain the zone, it's job is done
end
if persistence.verbose then
trigger.action.outText("+++persistence is active", 30)
end
-- we now see if we can and need load data
persistence.missionStartDataLoad()
-- and start updating
persistence.update()
return persistence.active
end
--
-- go!
--
if not persistence.start() then
if persistence.verbose then
trigger.action.outText("+++ persistence not available", 30)
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,5 +1,5 @@
radioMenu = {} radioMenu = {}
radioMenu.version = "1.0.1" radioMenu.version = "1.1.0"
radioMenu.verbose = false radioMenu.verbose = false
radioMenu.ups = 1 radioMenu.ups = 1
radioMenu.requiredLibs = { radioMenu.requiredLibs = {
@ -12,6 +12,9 @@ radioMenu.menus = {}
Version History Version History
1.0.0 Initial version 1.0.0 Initial version
1.0.1 spelling corrections 1.0.1 spelling corrections
1.1.0 removeMenu
addMenu
menuVisible
--]]-- --]]--
function radioMenu.addRadioMenu(theZone) function radioMenu.addRadioMenu(theZone)
@ -32,17 +35,60 @@ end
-- --
-- read zone -- read zone
-- --
function radioMenu.installMenu(theZone)
if theZone.coalition == 0 then
theZone.rootMenu = missionCommands.addSubMenu(theZone.rootName, nil)
else
theZone.rootMenu = missionCommands.addSubMenuForCoalition(theZone.coalition, theZone.rootName, nil)
end
local menuA = cfxZones.getStringFromZoneProperty(theZone, "itemA", "<no A submenu>")
if theZone.coalition == 0 then
theZone.menuA = missionCommands.addCommand(menuA, theZone.rootMenu, radioMenu.redirectMenuX, {theZone, "A"})
else
theZone.menuA = missionCommands.addCommandForCoalition(theZone.coalition, menuA, theZone.rootMenu, radioMenu.redirectMenuX, {theZone, "A"})
end
if cfxZones.hasProperty(theZone, "itemB") then
local menuB = cfxZones.getStringFromZoneProperty(theZone, "itemB", "<no B submenu>")
if theZone.coalition == 0 then
theZone.menuB = missionCommands.addCommand(menuB, theZone.rootMenu, radioMenu.redirectMenuX, {theZone, "B"})
else
theZone.menuB = missionCommands.addCommandForCoalition(theZone.coalition, menuB, theZone.rootMenu, radioMenu.redirectMenuX, {theZone, "B"})
end
end
if cfxZones.hasProperty(theZone, "itemC") then
local menuC = cfxZones.getStringFromZoneProperty(theZone, "itemC", "<no C submenu>")
if theZone.coalition == 0 then
theZone.menuC = missionCommands.addCommand(menuC, theZone.rootMenu, radioMenu.redirectMenuX, {theZone, "C"})
else
theZone.menuC = missionCommands.addCommandForCoalition(theZone.coalition, menuC, theZone.rootMenu, radioMenu.redirectMenuX, {theZone, "C"})
end
end
if cfxZones.hasProperty(theZone, "itemD") then
local menuD = cfxZones.getStringFromZoneProperty(theZone, "itemD", "<no D submenu>")
if theZone.coalition == 0 then
theZone.menuD = missionCommands.addCommand(menuD, theZone.rootMenu, radioMenu.redirectMenuX, {theZone, "D"})
else
theZone.menuD = missionCommands.addCommandForCoalition(theZone.coalition, menuD, theZone.rootMenu, radioMenu.redirectMenuX, {theZone, "D"})
end
end
end
function radioMenu.createRadioMenuWithZone(theZone) function radioMenu.createRadioMenuWithZone(theZone)
local rootName = cfxZones.getStringFromZoneProperty(theZone, "radioMenu", "<No Name>") theZone.rootName = cfxZones.getStringFromZoneProperty(theZone, "radioMenu", "<No Name>")
theZone.coalition = cfxZones.getCoalitionFromZoneProperty(theZone, "coalition", 0) theZone.coalition = cfxZones.getCoalitionFromZoneProperty(theZone, "coalition", 0)
if theZone.coalition == 0 then theZone.menuVisible = cfxZones.getBoolFromZoneProperty(theZone, "menuVisible", true)
theZone.rootMenu = missionCommands.addSubMenu(rootName, nil)
else
theZone.rootMenu = missionCommands.addSubMenuForCoalition(theZone.coalition, rootName, nil)
end
-- install menu if not hidden
if theZone.menuVisible then
radioMenu.installMenu(theZone)
end
--[[--
-- now do the two options -- now do the two options
local menuA = cfxZones.getStringFromZoneProperty(theZone, "itemA", "<no A submenu>") local menuA = cfxZones.getStringFromZoneProperty(theZone, "itemA", "<no A submenu>")
if theZone.coalition == 0 then if theZone.coalition == 0 then
@ -78,6 +124,7 @@ function radioMenu.createRadioMenuWithZone(theZone)
end end
end end
--]]--
-- get the triggers & methods here -- get the triggers & methods here
theZone.radioMethod = cfxZones.getStringFromZoneProperty(theZone, "method", "inc") theZone.radioMethod = cfxZones.getStringFromZoneProperty(theZone, "method", "inc")
@ -85,6 +132,8 @@ function radioMenu.createRadioMenuWithZone(theZone)
theZone.radioMethod = cfxZones.getStringFromZoneProperty(theZone, "radioMethod", "inc") theZone.radioMethod = cfxZones.getStringFromZoneProperty(theZone, "radioMethod", "inc")
end end
theZone.radioTriggerMethod = cfxZones.getStringFromZoneProperty(theZone, "radioTriggerMethod", "change")
theZone.itemAChosen = cfxZones.getStringFromZoneProperty(theZone, "A!", "*<none>") theZone.itemAChosen = cfxZones.getStringFromZoneProperty(theZone, "A!", "*<none>")
theZone.cooldownA = cfxZones.getNumberFromZoneProperty(theZone, "cooldownA", 0) theZone.cooldownA = cfxZones.getNumberFromZoneProperty(theZone, "cooldownA", 0)
theZone.mcdA = 0 theZone.mcdA = 0
@ -105,6 +154,16 @@ function radioMenu.createRadioMenuWithZone(theZone)
theZone.mcdD = 0 theZone.mcdD = 0
theZone.busyD = cfxZones.getStringFromZoneProperty(theZone, "busyD", "Please stand by (<s> seconds)") theZone.busyD = cfxZones.getStringFromZoneProperty(theZone, "busyD", "Please stand by (<s> seconds)")
if cfxZones.hasProperty(theZone, "removeMenu?") then
theZone.removeMenu = cfxZones.getStringFromZoneProperty(theZone, "removeMenu?", "*<none>")
theZone.lastRemoveMenu = cfxZones.getFlagValue(theZone.removeMenu, theZone)
end
if cfxZones.hasProperty(theZone, "addMenu?") then
theZone.addMenu = cfxZones.getStringFromZoneProperty(theZone, "addMenu?", "*<none>")
theZone.lastAddMenu = cfxZones.getFlagValue(theZone.addMenu, theZone)
end
if radioMenu.verbose or theZone.verbose then if radioMenu.verbose or theZone.verbose then
trigger.action.outText("+++radioMenu: new radioMenu zone <".. theZone.name ..">", 30) trigger.action.outText("+++radioMenu: new radioMenu zone <".. theZone.name ..">", 30)
end end
@ -189,13 +248,44 @@ end
-- --
-- Update -- required when we can enable/disable a zone's menu -- Update -- required when we can enable/disable a zone's menu
-- --
--[[--
function radioMenu.update() function radioMenu.update()
-- call me in a second to poll triggers -- call me in a second to poll triggers
timer.scheduleFunction(radioMenu.update, {}, timer.getTime() + 1/radioMenu.ups) timer.scheduleFunction(radioMenu.update, {}, timer.getTime() + 1/radioMenu.ups)
-- iterate all menus
for idx, theZone in pairs(radioMenu.menus) do
if theZone.removeMenu
and cfxZones.testZoneFlag(theZone, theZone.removeMenu, theZone.radioTriggerMethod, "lastRemoveMenu")
and theZone.menuVisible
then
if theZone.verbose or radioMenu.verbose then
trigger.action.outText("+++menu: removing <" .. dcsCommon.menu2text(theZone.rootMenu) .. "> for <" .. theZone.name .. ">", 30)
end
if theZone.coalition == 0 then
missionCommands.removeItem(theZone.rootMenu)
else
missionCommands.removeItemForCoalition(theZone.coalition, theZone.rootMenu)
end
theZone.menuVisible = false
end
if theZone.addMenu
and cfxZones.testZoneFlag(theZone, theZone.addMenu, theZone.radioTriggerMethod, "lastAddMenu")
and (not theZone.menuVisible)
then
if theZone.verbose or radioMenu.verbose then
trigger.action.outText("+++menu: adding menu from <" .. theZone.name .. ">", 30)
end
radioMenu.installMenu(theZone) -- auto-handles coalition
theZone.menuVisible = true
end
end
end end
--]]--
-- --
-- Config & Start -- Config & Start
@ -238,7 +328,7 @@ function radioMenu.start()
end end
-- start update -- start update
--radioMenu.update() radioMenu.update()
trigger.action.outText("cfx radioMenu v" .. radioMenu.version .. " started.", 30) trigger.action.outText("cfx radioMenu v" .. radioMenu.version .. " started.", 30)
return true return true
@ -251,7 +341,5 @@ if not radioMenu.start() then
end end
--[[-- --[[--
to do: turn on/off via flags
callbacks for the menus callbacks for the menus
one-shot items
--]]-- --]]--

View File

@ -1,13 +1,15 @@
-- theDebugger -- theDebugger
debugger = {} debugger = {}
debugger.version = "1.0.1" debugger.version = "1.1.1"
debugDemon = {} debugDemon = {}
debugDemon.version = "1.0.0" debugDemon.version = "1.1.1"
debugger.verbose = false debugger.verbose = false
debugger.ups = 4 -- every 0.25 second debugger.ups = 4 -- every 0.25 second
debugger.name = "DML Debugger" -- for compliance with cfxZones debugger.name = "DML Debugger" -- for compliance with cfxZones
debugger.log = ""
--[[-- --[[--
Version History Version History
1.0.0 - Initial version 1.0.0 - Initial version
@ -15,14 +17,67 @@ debugger.name = "DML Debugger" -- for compliance with cfxZones
- changed 'on' to 'active' in config zone - changed 'on' to 'active' in config zone
- merged debugger and debugDemon - merged debugger and debugDemon
- QoL check for 'debug' attribute (no '?') - QoL check for 'debug' attribute (no '?')
1.0.2 - contested! flag 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
--]]-- --]]--
debugger.requiredLibs = { debugger.requiredLibs = {
"dcsCommon", -- always "dcsCommon", -- always
"cfxZones", -- Zones, of course "cfxZones", -- Zones, of course
} }
-- note: saving logs requires persistence module
-- will auto-abort saving if not present
debugger.debugZones = {} debugger.debugZones = {}
debugger.debugUnits = {}
debugger.debugGroups = {}
debugger.debugObjects = {}
--
-- Logging & saving
--
function debugger.outText(message, seconds, cls)
if not message then message = "" end
if not seconds then seconds = 20 end
if not cls then cls = false end
-- append message to log, and add a lf
if not debugger.log then debugger.log = "" end
debugger.log = debugger.log .. message .. "\n"
-- now hand up to trigger
trigger.action.outText(message, seconds, cls)
end
function debugger.saveLog(name)
if not _G["persistence"] then
debugger.outText("+++debug: persistence module required to save log")
return
end
if not persistence.active then
debugger.outText("+++debug: persistence module can't write. ensur you desanitize lfs and io")
return
end
if persistence.saveText(debugger.log, name) then
debugger.outText("+++debug: log saved to <" .. persistence.missionDir .. name .. ">")
else
debugger.outText("+++debug: unable to save log to <" .. persistence.missionDir .. name .. ">")
end
end
--
-- tracking flags
--
function debugger.addDebugger(theZone) function debugger.addDebugger(theZone)
table.insert(debugger.debugZones, theZone) table.insert(debugger.debugZones, theZone)
@ -33,7 +88,7 @@ function debugger.getDebuggerByName(aName)
if aName == aZone.name then return aZone end if aName == aZone.name then return aZone end
end end
if debugger.verbose then if debugger.verbose then
trigger.action.outText("+++debug: no debug zone with name <" .. aName ..">", 30) debugger.outText("+++debug: no debug zone with name <" .. aName ..">", 30)
end end
return nil return nil
@ -65,7 +120,7 @@ function debugger.createDebuggerWithZone(theZone)
-- say who we are and what we are monitoring -- say who we are and what we are monitoring
if debugger.verbose or theZone.verbose then if debugger.verbose or theZone.verbose then
trigger.action.outText("---debug: adding zone <".. theZone.name .."> to look for <value " .. theZone.debugInputMethod .. "> in flag(s):", 30) debugger.outText("---debug: adding zone <".. theZone.name .."> to look for <value " .. theZone.debugInputMethod .. "> in flag(s):", 30)
end end
-- read main debug array -- read main debug array
@ -77,7 +132,7 @@ function debugger.createDebuggerWithZone(theZone)
for idx, aFlag in pairs(flagArray) do for idx, aFlag in pairs(flagArray) do
local fVal = cfxZones.getFlagValue(aFlag, theZone) local fVal = cfxZones.getFlagValue(aFlag, theZone)
if debugger.verbose or theZone.verbose then if debugger.verbose or theZone.verbose then
trigger.action.outText(" monitoring flag <" .. aFlag .. ">, inital value is <" .. fVal .. ">", 30) debugger.outText(" monitoring flag <" .. aFlag .. ">, inital value is <" .. fVal .. ">", 30)
end end
valueArray[aFlag] = fVal valueArray[aFlag] = fVal
end end
@ -226,7 +281,7 @@ function debugger.debugZone(theZone)
-- generate the ouput message -- generate the ouput message
local msg = theZone.debugMsg local msg = theZone.debugMsg
msg = debugger.processDebugMsg(msg, theZone, aFlag, oldVal, newValue) msg = debugger.processDebugMsg(msg, theZone, aFlag, oldVal, newValue)
trigger.action.outText(msg, 30) debugger.outText(msg, 30)
end end
end end
@ -239,7 +294,7 @@ function debugger.resetObserver(theZone)
for idf, aFlag in pairs(theZone.flagArray) do for idf, aFlag in pairs(theZone.flagArray) do
local fVal = cfxZones.getFlagValue(aFlag, theZone) local fVal = cfxZones.getFlagValue(aFlag, theZone)
if debugger.verbose or theZone.verbose then if debugger.verbose or theZone.verbose then
trigger.action.outText("---debug: resetting flag <" .. aFlag .. ">, to <" .. fVal .. "> for zone <" .. theZone.name .. ">", 30) debugger.outText("---debug: resetting flag <" .. aFlag .. ">, to <" .. fVal .. "> for zone <" .. theZone.name .. ">", 30)
end end
theZone.valueArray[aFlag] = fVal theZone.valueArray[aFlag] = fVal
end end
@ -256,39 +311,39 @@ function debugger.showObserverState(theZone)
for idf, aFlag in pairs(theZone.flagArray) do for idf, aFlag in pairs(theZone.flagArray) do
local fVal = cfxZones.getFlagValue(aFlag, theZone) local fVal = cfxZones.getFlagValue(aFlag, theZone)
if debugger.verbose or theZone.verbose then if debugger.verbose or theZone.verbose then
trigger.action.outText(" state of flag <" .. aFlag .. ">: <" .. theZone.valueArray[aFlag] .. ">", 30) debugger.outText(" state of flag <" .. aFlag .. ">: <" .. theZone.valueArray[aFlag] .. ">", 30)
end end
theZone.valueArray[aFlag] = fVal theZone.valueArray[aFlag] = fVal
end end
end end
function debugger.showState() function debugger.showState()
trigger.action.outText("---debug: CURRENT STATE <" .. dcsCommon.nowString() .. "> --- ", 30) debugger.outText("---debug: CURRENT STATE <" .. dcsCommon.nowString() .. "> --- ", 30)
for idx, theZone in pairs(debugger.debugZones) do for idx, theZone in pairs(debugger.debugZones) do
-- show this zone's state -- show this zone's state
if #theZone.flagArray > 0 then if #theZone.flagArray > 0 then
trigger.action.outText(" state of observer <" .. theZone.name .. "> looking for <value " .. theZone.debugInputMethod .. ">:", 30) debugger.outText(" state of observer <" .. theZone.name .. "> looking for <value " .. theZone.debugInputMethod .. ">:", 30)
debugger.showObserverState(theZone) debugger.showObserverState(theZone)
else else
if theZone.verbose or debugger.verbose then if theZone.verbose or debugger.verbose then
trigger.action.outText(" (empty observer <" .. theZone.name .. ">)", 30) debugger.outText(" (empty observer <" .. theZone.name .. ">)", 30)
end end
end end
end end
trigger.action.outText("---debug: end of state --- ", 30) debugger.outText("---debug: end of state --- ", 30)
end end
function debugger.doActivate() function debugger.doActivate()
debugger.active = true debugger.active = true
if debugger.verbose or true then if debugger.verbose or true then
trigger.action.outText("+++ DM Debugger is now active", 30) debugger.outText("+++ DM Debugger is now active", 30)
end end
end end
function debugger.doDeactivate() function debugger.doDeactivate()
debugger.active = false debugger.active = false
if debugger.verbose or true then if debugger.verbose or true then
trigger.action.outText("+++ debugger deactivated", 30) debugger.outText("+++ debugger deactivated", 30)
end end
end end
@ -339,7 +394,7 @@ function debugger.readConfigZone()
local theZone = cfxZones.getZoneByName("debuggerConfig") local theZone = cfxZones.getZoneByName("debuggerConfig")
if not theZone then if not theZone then
if debugger.verbose then if debugger.verbose then
trigger.action.outText("+++debug: NO config zone!", 30) debugger.outText("+++debug: NO config zone!", 30)
end end
theZone = cfxZones.createSimpleZone("debuggerConfig") theZone = cfxZones.createSimpleZone("debuggerConfig")
end end
@ -371,7 +426,7 @@ function debugger.readConfigZone()
debugger.ups = cfxZones.getNumberFromZoneProperty(theZone, "ups", 4) debugger.ups = cfxZones.getNumberFromZoneProperty(theZone, "ups", 4)
if debugger.verbose then if debugger.verbose then
trigger.action.outText("+++debug: read config", 30) debugger.outText("+++debug: read config", 30)
end end
end end
@ -398,22 +453,22 @@ function debugger.start()
local attrZones = cfxZones.getZonesWithAttributeNamed("debug") local attrZones = cfxZones.getZonesWithAttributeNamed("debug")
for k, aZone in pairs(attrZones) do for k, aZone in pairs(attrZones) do
trigger.action.outText("***Warning: Zone <" .. aZone.name .. "> has a 'debug' flag. Are you perhaps missing a '?'", 30) debugger.outText("***Warning: Zone <" .. aZone.name .. "> has a 'debug' flag. Are you perhaps missing a '?'", 30)
end end
-- say if we are active -- say if we are active
if debugger.verbose then if debugger.verbose then
if debugger.active then if debugger.active then
trigger.action.outText("+++debugger loaded and active", 30) debugger.outText("+++debugger loaded and active", 30)
else else
trigger.action.outText("+++ debugger: standing by for activation", 30) debugger.outText("+++ debugger: standing by for activation", 30)
end end
end end
-- start update -- start update
debugger.update() debugger.update()
trigger.action.outText("cfx debugger v" .. debugger.version .. " started.", 30) debugger.outText("cfx debugger v" .. debugger.version .. " started.", 30)
return true return true
end end
@ -441,6 +496,7 @@ debugDemon.verbose = false
--[[-- --[[--
Version History Version History
1.0.0 - initial version 1.0.0 - initial version
1.1.0 - save command, requires persistence
--]]-- --]]--
@ -559,7 +615,7 @@ function debugDemon.executeCommand(theCommands, event)
local success = theInvoker(arguments, event) local success = theInvoker(arguments, event)
return success return success
else else
trigger.action.outText("***error: unknown command <".. cmd .. ">", 30) debugger.outText("***error: unknown command <".. cmd .. ">", 30)
return false return false
end end
@ -592,7 +648,7 @@ end
-- COMMANDS -- COMMANDS
-- --
function debugDemon.processHelpCommand(args, event) function debugDemon.processHelpCommand(args, event)
trigger.action.outText("*** debugger: commands are:" .. debugger.outText("*** debugger: commands are:" ..
"\n " .. debugDemon.markOfDemon .. "show <flagname/observername> -- show current values for flag or observer" .. "\n " .. debugDemon.markOfDemon .. "show <flagname/observername> -- show current values for flag or observer" ..
"\n " .. debugDemon.markOfDemon .. "set <flagname> <number> -- set flag to value <number>" .. "\n " .. debugDemon.markOfDemon .. "set <flagname> <number> -- set flag to value <number>" ..
"\n " .. debugDemon.markOfDemon .. "inc <flagname> -- increase flag by 1, changing it" .. "\n " .. debugDemon.markOfDemon .. "inc <flagname> -- increase flag by 1, changing it" ..
@ -614,6 +670,8 @@ trigger.action.outText("*** debugger: commands are:" ..
"\n\n " .. debugDemon.markOfDemon .. "start -- starts debugger" .. "\n\n " .. debugDemon.markOfDemon .. "start -- starts debugger" ..
"\n " .. debugDemon.markOfDemon .. "stop -- stop debugger" .. "\n " .. debugDemon.markOfDemon .. "stop -- stop debugger" ..
"\n\n " .. debugDemon.markOfDemon .. "save [<filename>] -- saves debugger log to storage" ..
"\n\n " .. debugDemon.markOfDemon .. "? or -help -- this text", 30) "\n\n " .. debugDemon.markOfDemon .. "? or -help -- this text", 30)
return true return true
end end
@ -622,14 +680,14 @@ function debugDemon.processNewCommand(args, event)
-- syntax new <observername> [[for] <condition>] -- syntax new <observername> [[for] <condition>]
local observerName = args[1] local observerName = args[1]
if not observerName then if not observerName then
trigger.action.outText("*** new: missing observer name.", 30) debugger.outText("*** new: missing observer name.", 30)
return false -- allows correction return false -- allows correction
end end
-- see if this observer already existst -- see if this observer already existst
local theObserver = debugger.getDebuggerByName(observerName) local theObserver = debugger.getDebuggerByName(observerName)
if theObserver then if theObserver then
trigger.action.outText("*** new: observer <" .. observerName .. "> already exists.", 30) debugger.outText("*** new: observer <" .. observerName .. "> already exists.", 30)
return false -- allows correction return false -- allows correction
end end
@ -638,7 +696,7 @@ function debugDemon.processNewCommand(args, event)
local remainderName = event.remainder local remainderName = event.remainder
local rObserver = debugger.getDebuggerByName(remainderName) local rObserver = debugger.getDebuggerByName(remainderName)
if rObserver then if rObserver then
trigger.action.outText("*** new: observer <" .. remainderName .. "> already exists.", 30) debugger.outText("*** new: observer <" .. remainderName .. "> already exists.", 30)
return false -- allows correction return false -- allows correction
end end
@ -655,14 +713,14 @@ function debugDemon.processNewCommand(args, event)
if condition == "for" then condition = args[3] end if condition == "for" then condition = args[3] end
if condition then if condition then
if not cfxZones.verifyMethod(condition, theZone) then if not cfxZones.verifyMethod(condition, theZone) then
trigger.action.outText("*** new: illegal trigger condition <" .. condition .. "> for observer <" .. observerName .. ">", 30) debugger.outText("*** new: illegal trigger condition <" .. condition .. "> for observer <" .. observerName .. ">", 30)
return false return false
end end
theZone.debugInputMethod = condition theZone.debugInputMethod = condition
end end
debugger.addDebugger(theZone) debugger.addDebugger(theZone)
trigger.action.outText("*** [" .. dcsCommon.nowString() .. "] debugger: new observer <" .. observerName .. "> for <" .. theZone.debugInputMethod .. ">", 30) debugger.outText("*** [" .. dcsCommon.nowString() .. "] debugger: new observer <" .. observerName .. "> for <" .. theZone.debugInputMethod .. ">", 30)
return true return true
end end
@ -670,14 +728,14 @@ function debugDemon.processUpdateCommand(args, event)
-- syntax update <observername> [[to] <condition>] -- syntax update <observername> [[to] <condition>]
local observerName = args[1] local observerName = args[1]
if not observerName then if not observerName then
trigger.action.outText("*** update: missing observer name.", 30) debugger.outText("*** update: missing observer name.", 30)
return false -- allows correction return false -- allows correction
end end
-- see if this observer already existst -- see if this observer already existst
local theZone = debugger.getDebuggerByName(observerName) local theZone = debugger.getDebuggerByName(observerName)
if not theZone then if not theZone then
trigger.action.outText("*** update: observer <" .. observerName .. "> does not exist exists.", 30) debugger.outText("*** update: observer <" .. observerName .. "> does not exist exists.", 30)
return false -- allows correction return false -- allows correction
end end
@ -685,13 +743,13 @@ function debugDemon.processUpdateCommand(args, event)
if condition == "to" then condition = args[3] end if condition == "to" then condition = args[3] end
if condition then if condition then
if not cfxZones.verifyMethod(condition, theZone) then if not cfxZones.verifyMethod(condition, theZone) then
trigger.action.outText("*** update: illegal trigger condition <" .. condition .. "> for observer <" .. observerName .. ">", 30) debugger.outText("*** update: illegal trigger condition <" .. condition .. "> for observer <" .. observerName .. ">", 30)
return false return false
end end
theZone.debugInputMethod = condition theZone.debugInputMethod = condition
end end
trigger.action.outText("*** [" .. dcsCommon.nowString() .. "] debugger: updated observer <" .. observerName .. "> to <" .. theZone.debugInputMethod .. ">", 30) debugger.outText("*** [" .. dcsCommon.nowString() .. "] debugger: updated observer <" .. observerName .. "> to <" .. theZone.debugInputMethod .. ">", 30)
return true return true
end end
@ -699,21 +757,21 @@ function debugDemon.processDropCommand(args, event)
-- syntax drop <observername> -- syntax drop <observername>
local observerName = event.remainder -- remainder local observerName = event.remainder -- remainder
if not observerName then if not observerName then
trigger.action.outText("*** drop: missing observer name.", 30) debugger.outText("*** drop: missing observer name.", 30)
return false -- allows correction return false -- allows correction
end end
-- see if this observer already existst -- see if this observer already existst
local theZone = debugger.getDebuggerByName(observerName) local theZone = debugger.getDebuggerByName(observerName)
if not theZone then if not theZone then
trigger.action.outText("*** drop: observer <" .. observerName .. "> does not exist exists.", 30) debugger.outText("*** drop: observer <" .. observerName .. "> does not exist exists.", 30)
return false -- allows correction return false -- allows correction
end end
-- now simply and irrevocable remove the observer, unless it's home, -- now simply and irrevocable remove the observer, unless it's home,
-- in which case it's simply reset -- in which case it's simply reset
if theZone == debugDemon.observer then if theZone == debugDemon.observer then
trigger.action.outText("*** drop: <" .. observerName .. "> is MY PRECIOUS and WILL NOT be dropped.", 30) debugger.outText("*** drop: <" .. observerName .. "> is MY PRECIOUS and WILL NOT be dropped.", 30)
-- can't really happen since it contains blanks, but -- can't really happen since it contains blanks, but
-- we've seen stranger things -- we've seen stranger things
return false -- allows correction return false -- allows correction
@ -721,7 +779,7 @@ function debugDemon.processDropCommand(args, event)
debugger.removeDebugger(theZone) debugger.removeDebugger(theZone)
trigger.action.outText("*** [" .. dcsCommon.nowString() .. "] debugger: dropped observer <" .. observerName .. ">", 30) debugger.outText("*** [" .. dcsCommon.nowString() .. "] debugger: dropped observer <" .. observerName .. ">", 30)
return true return true
end end
-- observe command: add a new flag to observe -- observe command: add a new flag to observe
@ -730,7 +788,7 @@ function debugDemon.processObserveCommand(args, event)
-- args[1] is the name of the flag -- args[1] is the name of the flag
local flagName = args[1] local flagName = args[1]
if not flagName then if not flagName then
trigger.action.outText("*** observe: missing flag name.", 30) debugger.outText("*** observe: missing flag name.", 30)
return false -- allows correction return false -- allows correction
end end
@ -738,7 +796,7 @@ function debugDemon.processObserveCommand(args, event)
if args[2] == "with" then if args[2] == "with" then
local aName = args[3] local aName = args[3]
if not aName then if not aName then
trigger.action.outText("*** observe: missing <observer name> after 'with'.", 30) debugger.outText("*** observe: missing <observer name> after 'with'.", 30)
return false -- allows correction return false -- allows correction
end end
aName = dcsCommon.stringRemainsStartingWith(event.remainder, aName) aName = dcsCommon.stringRemainsStartingWith(event.remainder, aName)
@ -746,12 +804,12 @@ function debugDemon.processObserveCommand(args, event)
if not withTracker then if not withTracker then
-- withTracker = debugDemon.createObserver(aName) -- withTracker = debugDemon.createObserver(aName)
-- debugger.addDebugger(withTracker) -- debugger.addDebugger(withTracker)
trigger.action.outText("*** observe: no observer <" .. aName .. "> exists", 30) debugger.outText("*** observe: no observer <" .. aName .. "> exists", 30)
return false -- allows correction return false -- allows correction
end end
else -- not with as arg 2 else -- not with as arg 2
if #args > 1 then if #args > 1 then
trigger.action.outText("*** observe: unknown command after flag name '" .. flagName .. "'.", 30) debugger.outText("*** observe: unknown command after flag name '" .. flagName .. "'.", 30)
return false -- allows correction return false -- allows correction
end end
-- use own observer -- use own observer
@ -759,13 +817,13 @@ function debugDemon.processObserveCommand(args, event)
end end
if debugger.isObservingWithObserver(flagName, withTracker) then if debugger.isObservingWithObserver(flagName, withTracker) then
trigger.action.outText("*** observe: already observing " .. flagName .. " with <" .. withTracker.name .. ">" , 30) debugger.outText("*** observe: already observing " .. flagName .. " with <" .. withTracker.name .. ">" , 30)
return true return true
end end
-- we add flag to tracker and init value -- we add flag to tracker and init value
debugger.addFlagToObserver(flagName, withTracker) debugger.addFlagToObserver(flagName, withTracker)
trigger.action.outText("*** [" .. dcsCommon.nowString() .. "] debugger: now observing <" .. flagName .. "> for value " .. withTracker.debugInputMethod .. " with <" .. withTracker.name .. ">.", 30) debugger.outText("*** [" .. dcsCommon.nowString() .. "] debugger: now observing <" .. flagName .. "> for value " .. withTracker.debugInputMethod .. " with <" .. withTracker.name .. ">.", 30)
return true return true
end end
@ -774,7 +832,7 @@ function debugDemon.processShowCommand(args, event)
-- observer has precendce over flag -- observer has precendce over flag
local theName = args[1] local theName = args[1]
if not theName then if not theName then
trigger.action.outText("*** show: missing observer/flag name.", 30) debugger.outText("*** show: missing observer/flag name.", 30)
return false -- allows correction return false -- allows correction
end end
@ -785,12 +843,12 @@ function debugDemon.processShowCommand(args, event)
if not theObserver then if not theObserver then
-- we directly use trigger.misc -- we directly use trigger.misc
local fVal = trigger.misc.getUserFlag(theName) local fVal = trigger.misc.getUserFlag(theName)
trigger.action.outText("[" .. dcsCommon.nowString() .. "] flag <" .. theName .. "> : value <".. fVal .. ">", 30) debugger.outText("[" .. dcsCommon.nowString() .. "] flag <" .. theName .. "> : value <".. fVal .. ">", 30)
return true return true
end end
-- if we get here, we want to show an entire observer -- if we get here, we want to show an entire observer
trigger.action.outText("*** [" .. dcsCommon.nowString() .. "] flags observed by <" .. theName .. "> looking for <value ".. theObserver.debugInputMethod .. ">:", 30) debugger.outText("*** [" .. dcsCommon.nowString() .. "] flags observed by <" .. theName .. "> looking for <value ".. theObserver.debugInputMethod .. ">:", 30)
local flags = theObserver.flagArray local flags = theObserver.flagArray
local values = theObserver.valueArray local values = theObserver.valueArray
for idx, flagName in pairs(flags) do for idx, flagName in pairs(flags) do
@ -804,7 +862,7 @@ function debugDemon.processShowCommand(args, event)
theMark = " ! " theMark = " ! "
trailer = ", HIT!" trailer = ", HIT!"
end end
trigger.action.outText(theMark .. "f:<" .. flagName .. "> = <".. fVal .. "> [current, state = <" .. values[flagName] .. ">" .. trailer .. "]", 30) debugger.outText(theMark .. "f:<" .. flagName .. "> = <".. fVal .. "> [current, state = <" .. values[flagName] .. ">" .. trailer .. "]", 30)
end end
return true return true
@ -836,7 +894,7 @@ function debugDemon.processSnapCommand(args, event)
theName = dcsCommon.stringRemainsStartingWith(event.remainder, theName) theName = dcsCommon.stringRemainsStartingWith(event.remainder, theName)
theObserver = debugger.getDebuggerByName(theName) theObserver = debugger.getDebuggerByName(theName)
if not theObserver then if not theObserver then
trigger.action.outText("*** snap: unknown observer name <" .. theName .. ">.", 30) debugger.outText("*** snap: unknown observer name <" .. theName .. ">.", 30)
return false -- allows correction return false -- allows correction
end end
end end
@ -861,26 +919,26 @@ function debugDemon.processSnapCommand(args, event)
local sz = dcsCommon.getSizeOfTable(snapshot) local sz = dcsCommon.getSizeOfTable(snapshot)
debugDemon.snapshot = snapshot debugDemon.snapshot = snapshot
trigger.action.outText("*** [" .. dcsCommon.nowString() .. "] debug: new snapshot created, " .. sz .. " flags.", 30) debugger.outText("*** [" .. dcsCommon.nowString() .. "] debug: new snapshot created, " .. sz .. " flags.", 30)
return true return true
end end
function debugDemon.processCompareCommand(args, event) function debugDemon.processCompareCommand(args, event)
trigger.action.outText("*** [" .. dcsCommon.nowString() .. "] debug: comparing snapshot with current flag values", 30) debugger.outText("*** [" .. dcsCommon.nowString() .. "] debug: comparing snapshot with current flag values", 30)
for flagName, val in pairs (debugDemon.snapshot) do for flagName, val in pairs (debugDemon.snapshot) do
local cVal = trigger.misc.getUserFlag(flagName) local cVal = trigger.misc.getUserFlag(flagName)
local mark = ' ' local mark = ' '
if cVal ~= val then mark = ' ! ' end if cVal ~= val then mark = ' ! ' end
trigger.action.outText(mark .. "<" .. flagName .. "> snap = <" .. val .. ">, now = <" .. cVal .. "> " .. mark, 30) debugger.outText(mark .. "<" .. flagName .. "> snap = <" .. val .. ">, now = <" .. cVal .. "> " .. mark, 30)
end end
trigger.action.outText("*** END", 30) debugger.outText("*** END", 30)
return true return true
end end
function debugDemon.processNoteCommand(args, event) function debugDemon.processNoteCommand(args, event)
local n = event.remainder local n = event.remainder
trigger.action.outText("*** [" .. dcsCommon.nowString() .. "]: " .. n, 30) debugger.outText("*** [" .. dcsCommon.nowString() .. "]: " .. n, 30)
return true return true
end end
@ -888,7 +946,7 @@ function debugDemon.processSetCommand(args, event)
-- syntax set <flagname> <value> -- syntax set <flagname> <value>
local theName = args[1] local theName = args[1]
if not theName then if not theName then
trigger.action.outText("*** set: missing flag name.", 30) debugger.outText("*** set: missing flag name.", 30)
return false -- allows correction return false -- allows correction
end end
@ -900,13 +958,21 @@ function debugDemon.processSetCommand(args, event)
end end
if not theVal or not (tonumber(theVal)) then if not theVal or not (tonumber(theVal)) then
trigger.action.outText("*** set: missing or illegal value for flag <" .. theName .. ">.", 30) debugger.outText("*** set: missing or illegal value for flag <" .. theName .. ">.", 30)
return false -- allows correction return false -- allows correction
end end
-- we set directly, no cfxZones procing theVal = tonumber(theVal)
trigger.action.outText("*** [" .. dcsCommon.nowString() .. "] debug: set flag <" .. theName .. "> to <" .. theVal .. ">", 30)
trigger.action.setUserFlag(theName, theVal) trigger.action.setUserFlag(theName, theVal)
-- we set directly, no cfxZones proccing
local note =""
-- flags are ints only?
if theVal ~= math.floor(theVal) then
note = " [int! " .. math.floor(theVal) .. "]"
end
debugger.outText("*** [" .. dcsCommon.nowString() .. "] debug: set flag <" .. theName .. "> to <" .. theVal .. ">" .. note, 30)
return true return true
end end
@ -914,7 +980,7 @@ function debugDemon.processIncCommand(args, event)
-- syntax inc <flagname> -- syntax inc <flagname>
local theName = args[1] local theName = args[1]
if not theName then if not theName then
trigger.action.outText("*** inc: missing flag name.", 30) debugger.outText("*** inc: missing flag name.", 30)
return false -- allows correction return false -- allows correction
end end
@ -922,7 +988,7 @@ function debugDemon.processIncCommand(args, event)
local nVal = cVal + 1 local nVal = cVal + 1
-- we set directly, no cfxZones procing -- we set directly, no cfxZones procing
trigger.action.outText("*** [" .. dcsCommon.nowString() .. "] debug: inc flag <" .. theName .. "> from <" .. cVal .. "> to <" .. nVal .. ">", 30) debugger.outText("*** [" .. dcsCommon.nowString() .. "] debug: inc flag <" .. theName .. "> from <" .. cVal .. "> to <" .. nVal .. ">", 30)
trigger.action.setUserFlag(theName, nVal) trigger.action.setUserFlag(theName, nVal)
return true return true
end end
@ -931,7 +997,7 @@ function debugDemon.processFlipCommand(args, event)
-- syntax flip <flagname> -- syntax flip <flagname>
local theName = args[1] local theName = args[1]
if not theName then if not theName then
trigger.action.outText("*** flip: missing flag name.", 30) debugger.outText("*** flip: missing flag name.", 30)
return false -- allows correction return false -- allows correction
end end
@ -939,7 +1005,7 @@ function debugDemon.processFlipCommand(args, event)
if cVal == 0 then nVal = 1 else nVal = 0 end if cVal == 0 then nVal = 1 else nVal = 0 end
-- we set directly, no cfxZones procing -- we set directly, no cfxZones procing
trigger.action.outText("*** [" .. dcsCommon.nowString() .. "] debug: flipped flag <" .. theName .. "> from <" .. cVal .. "> to <" .. nVal .. ">", 30) debugger.outText("*** [" .. dcsCommon.nowString() .. "] debug: flipped flag <" .. theName .. "> from <" .. cVal .. "> to <" .. nVal .. ">", 30)
trigger.action.setUserFlag(theName, nVal) trigger.action.setUserFlag(theName, nVal)
return true return true
end end
@ -952,9 +1018,9 @@ function debugDemon.processListCommand(args, event)
prefix = event.remainder -- dcsCommon.stringRemainsStartingWith(event.text, prefix) prefix = event.remainder -- dcsCommon.stringRemainsStartingWith(event.text, prefix)
end end
if prefix then if prefix then
trigger.action.outText("*** [" .. dcsCommon.nowString() .. "] listing observers whose name contains <" .. prefix .. ">:", 30) debugger.outText("*** [" .. dcsCommon.nowString() .. "] listing observers whose name contains <" .. prefix .. ">:", 30)
else else
trigger.action.outText("*** [" .. dcsCommon.nowString() .. "] listing all observers:", 30) debugger.outText("*** [" .. dcsCommon.nowString() .. "] listing all observers:", 30)
end end
local allObservers = debugger.debugZones local allObservers = debugger.debugZones
@ -966,7 +1032,7 @@ function debugDemon.processListCommand(args, event)
end end
if doList then if doList then
trigger.action.outText(" <" .. theName .. "> for <value " .. theZone.debugInputMethod .. "> (" .. #theZone.flagArray .. " flags)", 30) debugger.outText(" <" .. theName .. "> for <value " .. theZone.debugInputMethod .. "> (" .. #theZone.flagArray .. " flags)", 30)
end end
end end
return true return true
@ -976,20 +1042,20 @@ function debugDemon.processWhoCommand(args, event)
-- syntax: who <flagname> -- syntax: who <flagname>
local flagName = event.remainder -- args[1] local flagName = event.remainder -- args[1]
if not flagName or flagName:len()<1 then if not flagName or flagName:len()<1 then
trigger.action.outText("*** who: missing flag name.", 30) debugger.outText("*** who: missing flag name.", 30)
return false -- allows correction return false -- allows correction
end end
local observers = debugger.isObserving(flagName) local observers = debugger.isObserving(flagName)
if not observers or #observers < 1 then if not observers or #observers < 1 then
trigger.action.outText("*** [" .. dcsCommon.nowString() .. "] flag <" .. flagName .. "> is currently not observed", 30) debugger.outText("*** [" .. dcsCommon.nowString() .. "] flag <" .. flagName .. "> is currently not observed", 30)
return false return false
end end
trigger.action.outText("*** [" .. dcsCommon.nowString() .. "] flag <" .. flagName .. "> is currently observed by", 30) debugger.outText("*** [" .. dcsCommon.nowString() .. "] flag <" .. flagName .. "> is currently observed by", 30)
for idx, theZone in pairs(observers) do for idx, theZone in pairs(observers) do
trigger.action.outText(" <" .. theZone.name .. "> looking for <value " .. theZone.debugInputMethod .. ">", 30) debugger.outText(" <" .. theZone.name .. "> looking for <value " .. theZone.debugInputMethod .. ">", 30)
end end
return true return true
@ -1001,7 +1067,7 @@ function debugDemon.processForgetCommand(args, event)
local flagName = args[1] local flagName = args[1]
if not flagName then if not flagName then
trigger.action.outText("*** forget: missing flag name.", 30) debugger.outText("*** forget: missing flag name.", 30)
return false -- allows correction return false -- allows correction
end end
@ -1009,19 +1075,19 @@ function debugDemon.processForgetCommand(args, event)
if args[2] == "with" or args[2] == "from" then -- we also allow 'from' if args[2] == "with" or args[2] == "from" then -- we also allow 'from'
local aName = args[3] local aName = args[3]
if not aName then if not aName then
trigger.action.outText("*** forget: missing <observer name> after 'with'.", 30) debugger.outText("*** forget: missing <observer name> after 'with'.", 30)
return false -- allows correction return false -- allows correction
end end
aName = dcsCommon.stringRemainsStartingWith(event.remainder, aName) aName = dcsCommon.stringRemainsStartingWith(event.remainder, aName)
withTracker = debugger.getDebuggerByName(aName) withTracker = debugger.getDebuggerByName(aName)
if not withTracker then if not withTracker then
trigger.action.outText("*** forget: no observer named <" .. aName .. ">", 30) debugger.outText("*** forget: no observer named <" .. aName .. ">", 30)
return false return false
end end
else -- not with as arg 2 else -- not with as arg 2
if #args > 1 then if #args > 1 then
trigger.action.outText("*** forget: unknown command after flag name '" .. flagName .. "'.", 30) debugger.outText("*** forget: unknown command after flag name '" .. flagName .. "'.", 30)
return false -- allows correction return false -- allows correction
end end
-- use own observer -- use own observer
@ -1029,13 +1095,13 @@ function debugDemon.processForgetCommand(args, event)
end end
if not debugger.isObservingWithObserver(flagName, withTracker) then if not debugger.isObservingWithObserver(flagName, withTracker) then
trigger.action.outText("*** forget: observer <" .. withTracker.name .. "> does not observe flag <" .. flagName .. ">", 30) debugger.outText("*** forget: observer <" .. withTracker.name .. "> does not observe flag <" .. flagName .. ">", 30)
return false return false
end end
-- we add flag to tracker and init value -- we add flag to tracker and init value
debugger.removeFlagFromObserver(flagName, withTracker) debugger.removeFlagFromObserver(flagName, withTracker)
trigger.action.outText("*** [" .. dcsCommon.nowString() .. "] debugger: no longer observing " .. flagName .. " with <" .. withTracker.name .. ">.", 30) debugger.outText("*** [" .. dcsCommon.nowString() .. "] debugger: no longer observing " .. flagName .. " with <" .. withTracker.name .. ">.", 30)
return true return true
end end
@ -1057,25 +1123,37 @@ function debugDemon.processResetCommand(args, event)
local obsName = args[1] local obsName = args[1]
if not obsName then if not obsName then
debugger.reset() -- reset all debugger.reset() -- reset all
trigger.action.outText("*** [" .. dcsCommon.nowString() .. "] debug: reset complete.", 30) debugger.outText("*** [" .. dcsCommon.nowString() .. "] debug: reset complete.", 30)
return true -- allows correction return true -- allows correction
end end
local withTracker = nil local withTracker = nil
--if args[2] == "with" then local aName = event.remainder
local aName = args[1]
aName = event.remainder -- dcsCommon.stringRemainsStartingWith(event.text, aName)
withTracker = debugger.getDebuggerByName(aName) withTracker = debugger.getDebuggerByName(aName)
if not withTracker then if not withTracker then
trigger.action.outText("*** reset: no observer <" .. aName .. ">", 30) debugger.outText("*** reset: no observer <" .. aName .. ">", 30)
return false return false
end end
debugger.resetObserver(withTracker) debugger.resetObserver(withTracker)
trigger.action.outText("*** [" .. dcsCommon.nowString() .. "] debugger:reset observer <" .. withTracker.name .. ">", 30) debugger.outText("*** [" .. dcsCommon.nowString() .. "] debugger:reset observer <" .. withTracker.name .. ">", 30)
return true return true
end end
function debugDemon.processSaveCommand(args, event)
-- save log to file, requires persistence module
-- syntax: -save [<fileName>]
local aName = event.remainder
if not aName or aName:len() < 1 then
aName = "DML Debugger Log"
end
if not dcsCommon.stringEndsWith(aName, ".txt") then
aName = aName .. ".txt"
end
debugger.saveLog(aName)
return true
end
-- --
-- init and start -- init and start
-- --
@ -1084,7 +1162,7 @@ function debugDemon.readConfigZone()
local theZone = cfxZones.getZoneByName("debugDemonConfig") local theZone = cfxZones.getZoneByName("debugDemonConfig")
if not theZone then if not theZone then
if debugDemon.verbose then if debugDemon.verbose then
trigger.action.outText("+++debug: NO config zone!", 30) debugger.outText("+++debug (daemon): NO config zone!", 30)
end end
theZone = cfxZones.createSimpleZone("debugDemonConfig") theZone = cfxZones.createSimpleZone("debugDemonConfig")
end end
@ -1099,7 +1177,7 @@ function debugDemon.readConfigZone()
if debugger.verbose then if debugger.verbose then
trigger.action.outText("+++debug (deamon): read config", 30) debugger.outText("+++debug (deamon): read config", 30)
end end
end end
@ -1139,6 +1217,8 @@ function debugDemon.init()
debugDemon.addCommndProcessor("start", debugDemon.processStartCommand) debugDemon.addCommndProcessor("start", debugDemon.processStartCommand)
debugDemon.addCommndProcessor("stop", debugDemon.processStopCommand) debugDemon.addCommndProcessor("stop", debugDemon.processStopCommand)
debugDemon.addCommndProcessor("reset", debugDemon.processResetCommand) debugDemon.addCommndProcessor("reset", debugDemon.processResetCommand)
debugDemon.addCommndProcessor("save", debugDemon.processSaveCommand)
debugDemon.addCommndProcessor("?", debugDemon.processHelpCommand) debugDemon.addCommndProcessor("?", debugDemon.processHelpCommand)
debugDemon.addCommndProcessor("help", debugDemon.processHelpCommand) debugDemon.addCommndProcessor("help", debugDemon.processHelpCommand)
@ -1159,7 +1239,11 @@ function debugDemon.start()
debugDemon.snapshot = debugDemon.createSnapshot(debugger.debugZones) debugDemon.snapshot = debugDemon.createSnapshot(debugger.debugZones)
debugDemon.demonID = world.addEventHandler(debugDemon) debugDemon.demonID = world.addEventHandler(debugDemon)
trigger.action.outText("interactive debugDemon v" .. debugDemon.version .. " started" .. "\n enter " .. debugDemon.markOfDemon .. "? in a map mark for help", 30) debugger.outText("interactive debugDemon v" .. debugDemon.version .. " started" .. "\n enter " .. debugDemon.markOfDemon .. "? in a map mark for help", 30)
if not _G["persistence"] then
debugger.outText("\n note: '-save' disabled, no persistence module found", 30)
end
end end
if debugDemon.init() then if debugDemon.init() then
@ -1170,6 +1254,11 @@ else
end end
--[[-- --[[--
- track units/groups: health changes - track units/groups/objects: health changes
- track players: unit change - track players: unit change, enter, exit
- inspect objects, dumping category, life, if it's tasking, latLon, alt, speed, direction
- exec files. save all commands and then run them from script
- remove units via delete and explode
--]]-- --]]--

276
modules/unitPersistence.lua Normal file
View File

@ -0,0 +1,276 @@
unitPersistence = {}
unitPersistence.version = '1.0.0'
unitPersistence.verbose = false
unitPersistence.requiredLibs = {
"dcsCommon", -- always
"cfxZones", -- Zones, of course
"persistence",
"cfxMX",
}
--[[--
Version History
1.0.0 - initial version
REQUIRES PERSISTENCE AND MX
Persist ME-placed ground units
--]]--
unitPersistence.groundTroops = {} -- local buffered copy that we
-- maintain from save to save
unitPersistence.statics = {} -- locally unpacked and buffered static objects
--
-- Save -- Callback
--
function unitPersistence.saveData()
local theData = {}
if unitPersistence.verbose then
trigger.action.outText("+++unitPersistence: enter saveData", 30)
end
-- theData contains last save
-- we save GROUND units placed by ME on. we access a copy of MX data
-- for ground troups, iterate through all groups, and create
-- a replacement group here and now that is used to replace the one
-- that is there when it was spawned
for groupName, groupData in pairs(unitPersistence.groundTroops) do
-- we update this record live and save it to file
if not groupData.isDead then
local gotALiveOne = false
local allUnits = groupData.units
for idx, theUnitData in pairs(allUnits) do
if not theUnitData.isDead then
local uName = theUnitData.name
local gUnit = Unit.getByName(uName)
if gUnit and gUnit:isExist() then
-- got a live one!
gotALiveOne = true
-- update x and y and heading
theUnitData.heading = dcsCommon.getUnitHeading(gUnit)
pos = gUnit:getPoint()
theUnitData.x = pos.x
theUnitData.y = pos.z -- (!!)
-- ground units do not use alt
else
theUnitData.isDead = true
end -- is alive and exists?
end -- unit not dead
end -- iterate units in group
groupData.isDead = not gotALiveOne
end -- if group is not dead
if unitPersistence.verbose then
trigger.action.outText("unitPersistence: save - processed group <" .. groupName .. ">.", 30)
end
end
-- process all static objects placed with ME
for oName, oData in pairs(unitPersistence.statics) do
if not oData.isDead then
-- fetch the object and see if it's still alive
local theObject = StaticObject.getByName(oName)
if theObject and theObject:isExist() then
oData.heading = dcsCommon.getUnitHeading(theObject)
pos = theObject:getPoint()
oData.x = pos.x
oData.y = pos.z -- (!!)
oData.isDead = theObject:getLife() < 1
-- trigger.action.outText("deadcheck: " .. oName .. " has health=" .. theObject:getLife(), 30)
oData.dead = oData.isDead
else
oData.isDead = true
oData.dead = true
-- trigger.action.outText("deadcheck: " .. oName .. " certified dead", 30)
end
end
if unitPersistence.verbose then
local note = "(ok)"
if oData.isDead then note = "(dead)" end
trigger.action.outText("unitPersistence: save - processed group <" .. oName .. ">. " .. note, 30)
end
end
theData.version = unitPersistence.version
theData.ground = unitPersistence.groundTroops
theData.statics = unitPersistence.statics
return theData
end
--
-- Load Mission Data
--
function unitPersistence.loadMission()
local theData = persistence.getSavedDataForModule("unitPersistence")
if not theData then
if unitPersistence.verbose then
trigger.action.outText("unitPersistence: no save date received, skipping.", 30)
end
return
end
if theData.version ~= unitPersistence.version then
trigger.action.outText("\nWARNING!\nUnit data was saved with a different (older) version!\nProceed with caution, fresh start is recommended.\n", 30)
end
-- we just loaded an updated version of unitPersistence.groundTroops
-- now iterate all groups, update their positions and
-- delete all dead groups or units
-- because they currently should exist is the game
-- note: if they don't exist in-game that is because mission was
-- edited after last save
local mismatchWarning = false
if theData.ground then
for groupName, groupData in pairs(theData.ground) do
local theGroup = Group.getByName(groupName)
if not theGroup then
mismatchWarning = true
elseif groupData.isDead then
theGroup:destroy()
else
local newGroup = dcsCommon.clone(groupData)
local newUnits = {}
for idx, theUnitData in pairs(groupData.units) do
-- filter all dead groups
if theUnitData.isDead then
-- skip it
else
-- add it to new group
table.insert(newUnits, theUnitData)
end
end
-- replace old unit setup with new
newGroup.units = newUnits
local cty = groupData.cty
local cat = groupData.cat
-- destroy the old group
--theGroup:destroy() -- will be replaced
-- spawn new one
theGroup = coalition.addGroup(cty, cat, newGroup)
if not theGroup then
trigger.action.outText("+++ failed to add modified group <" .. groupName .. ">")
end
if unitPersistence.verbose then
trigger.action.outText("+++unitPersistence: updated group <" .. groupName .. "> of cat <" .. cat .. "> for cty <" .. cty .. ">", 30)
end
end
end
else
if unitPersistence.verbose then
trigger.action.outText("+++unitPersistence: no ground unit data.", 30)
end
end
-- and now the same for static objects
if theData.statics then
for name, staticData in pairs(theData.statics) do
local theStatic = StaticObject.getByName(name)
if not theStatic then
mismatchWarning = true
else
local newStatic = dcsCommon.clone(staticData)
local cty = staticData.cty
local cat = staticData.cat
-- spawn new one, replacing same.named old, dead if required
gStatic = coalition.addStaticObject(cty, newStatic)
if not gStatic then
trigger.action.outText("+++ failed to add modified static <" .. name .. ">")
end
if unitPersistence.verbose then
local note = ""
if newStatic.dead then note = " (dead)" end
trigger.action.outText("+++unitPersistence: updated static <" .. name .. "> for cty <" .. cty .. ">" .. note, 30)
end
end
end
end
if mismatchWarning then
trigger.action.outText("\n+++WARNING: \nSaved data does not match mission. You should re-start from scratch\n", 30)
end
-- set mission according to data received from last save
if unitPersistence.verbose then
trigger.action.outText("unitPersistence: units set from save data.", 30)
end
end
--
-- Start
--
function unitPersistence.start()
-- lib check
if (not dcsCommon) or (not dcsCommon.libCheck) then
trigger.action.outText("unit persistence requires dcsCommon", 30)
return false
end
if not dcsCommon.libCheck("unit persistence", unitPersistence.requiredLibs) then
return false
end
-- see if we even need to persist
if not persistence.active then
return true -- WARNING: true, but not really
end
-- sign up for save callback
callbacks = {}
callbacks.persistData = unitPersistence.saveData
persistence.registerModule("unitPersistence", callbacks)
-- create a local copy of the entire groundForces data that
-- we maintain internally. It's fixed, and we work on our
-- own copy for speed
for gname, data in pairs(cfxMX.allGroundByName) do
local gd = dcsCommon.clone(data) -- copy the record
gd.isDead = false -- init new field to alive
-- coalition and country
gd.cat = cfxMX.catText2ID("vehicle")
local gGroup = Group.getByName(gname)
if not gGroup then
trigger.action.outText("+++warning: group <" .. gname .. "> does not exist in-game!?", 30)
else
local firstUnit = gGroup:getUnit(1)
gd.cty = firstUnit:getCountry()
unitPersistence.groundTroops[gname] = gd
end
end
-- make local copies of all static MX objects
-- that we also maintain internally, and convert them to game
-- spawnable objects
for name, mxData in pairs(cfxMX.allStaticByName) do
-- statics in MX are built like groups, so we have to strip
-- the outer shell and extract all 'units' which are actually
-- objects. And there is usually only one
for idx, staticData in pairs(mxData.units) do
local theStatic = dcsCommon.clone(staticData)
theStatic.isDead = false
theStatic.groupId = mxData.groupId
theStatic.cat = cfxMX.catText2ID("static")
local gameOb = StaticObject.getByName(theStatic.name)
if not gameOb then
trigger.action.outText("+++warning: static object <" .. theStatic.name .. "> does not exist in-game!?", 30)
else
theStatic.cty = gameOb:getCountry()
unitPersistence.statics[theStatic.name] = theStatic
end
end
end
-- when we run, persistence has run and may have data ready for us
if persistence.hasData then
unitPersistence.loadMission()
end
return true
end
if not unitPersistence.start() then
if unitPersistence.verbose then
trigger.action.outText("+++ unit persistence not available", 30)
end
unitPersistence = nil
end

Binary file not shown.