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

View File

@ -1,5 +1,5 @@
cfxCargoReceiver = {}
cfxCargoReceiver.version = "1.2.1"
cfxCargoReceiver.version = "1.2.2"
cfxCargoReceiver.ups = 1 -- once a second
cfxCargoReceiver.maxDirectionRange = 500 -- in m. distance when cargo manager starts talking to pilots who are carrying that cargo
cfxCargoReceiver.requiredLibs = {
@ -17,6 +17,9 @@ cfxCargoReceiver.requiredLibs = {
- 1.2.0 method
f!, cargoReceived!
- 1.2.1 cargoMethod
- 1.2.2 removed deprecated functions
corrected pollFlag bug (not passing zone along)
distance to receiver is given as distance to zone boundary
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
aZone.isCargoReceiver = true
-- 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)
--trigger.action.outText("+++rcv: recognized receiver zone: " .. aZone.name , 30)
-- same integration as object destruct detector for flags
--[[--
if cfxZones.hasProperty(aZone, "setFlag") then
aZone.setFlag = cfxZones.getStringFromZoneProperty(aZone, "setFlag", "999")
end
@ -70,7 +75,7 @@ function cfxCargoReceiver.processReceiverZone(aZone) -- process attribute and ad
if cfxZones.hasProperty(aZone, "f-1") then
aZone.decreaseFlag = cfxZones.getStringFromZoneProperty(aZone, "f-1", "999")
end
--]]--
-- new method support
aZone.cargoMethod = cfxZones.getStringFromZoneProperty(aZone, "method", "inc")
if cfxZones.hasProperty(aZone, "cargoMethod") then
@ -79,9 +84,7 @@ function cfxCargoReceiver.processReceiverZone(aZone) -- process attribute and ad
if cfxZones.hasProperty(aZone, "f!") then
aZone.outReceiveFlag = cfxZones.getStringFromZoneProperty(aZone, "f!", "*<none>")
end
if cfxZones.hasProperty(aZone, "cargoReceived!") then
elseif cfxZones.hasProperty(aZone, "cargoReceived!") then
aZone.outReceiveFlag = cfxZones.getStringFromZoneProperty(aZone, "cargoReceived!", "*<none>")
end
@ -112,7 +115,25 @@ end
--
-- 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)
-- usually called from cargomanager
--trigger.action.outText("Cargo Receiver: event <" .. event .. "> for " .. name, 30)
if not event then return end
if event == "grounded" then
@ -135,6 +156,7 @@ function cfxCargoReceiver.cargoEvent(event, object, name)
cfxCargoReceiver.invokeCallback("deliver", object, name, aZone)
-- set flags as indicated
--[[--
if aZone.setFlag then
trigger.action.setUserFlag(aZone.setFlag, 1)
end
@ -149,16 +171,20 @@ function cfxCargoReceiver.cargoEvent(event, object, name)
local val = trigger.misc.getUserFlag(aZone.decreaseFlag) - 1
trigger.action.setUserFlag(aZone.decreaseFlag, val)
end
--]]--
if aZone.outReceiveFlag then
cfxZones.pollFlag(aZone.outReceiveFlag, aZone.cargoMethod)
cfxZones.pollFlag(aZone.outReceiveFlag, aZone.cargoMethod, aZone)
end
--trigger.action.outText("+++rcv: " .. name .. " delivered in zone " .. aZone.name, 30)
--trigger.action.outSound("Quest Snare 3.wav")
if aZone.autoRemove then
-- maybe schedule this in a few seconds?
object:destroy()
-- schedule this for in a few seconds?
local args = {}
args.theObject = object
args.theZone = aZone
timer.scheduleFunction(cfxCargoReceiver.removeCargo, args, timer.getTime() + aZone.removeDelay)
--object:destroy()
end
end
end
@ -177,11 +203,16 @@ function cfxCargoReceiver.update()
-- new we see if any of these are close to a delivery zone
for idx, aCargo in pairs(liftedCargos) do
local thePoint = aCargo:getPoint()
local receiver, delta = cfxZones.getClosestZone(
local receiver = cfxZones.getClosestZone(
thePoint,
cfxCargoReceiver.receiverZones -- must be indexed by name
)
-- 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
(delta < cfxCargoReceiver.maxDirectionRange) then
-- this cargo can be talked down.
@ -195,7 +226,7 @@ function cfxCargoReceiver.update()
local theUnit = info.unit
if theUnit:isExist() then
local uPoint = theUnit:getPoint()
local currDelta = dcsCommon.dist(thePoint, uPoint)
local currDelta = dcsCommon.distFlat(thePoint, uPoint)
if currDelta < minDelta then
minDelta = currDelta
closestUnit = theUnit
@ -217,7 +248,7 @@ function cfxCargoReceiver.update()
receiver.point,
thePoint,
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
-- add agl
local agl = dcsCommon.getUnitAGL(aCargo)

View File

@ -1,5 +1,6 @@
cfxMX = {}
cfxMX.version = "1.1.0"
cfxMX.version = "1.2.0"
cfxMX.verbose = false
--[[--
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
original group id
- add linkUnit for statics
1.2.0 - added group name reference table
- added group type reference
- added references for allFixed, allHelo, allGround, allSea, allStatic
--]]--
@ -19,6 +24,11 @@ cfxMX.groupNamesByID = {}
cfxMX.groupIDbyName = {}
cfxMX.groupDataByName = {}
cfxMX.allFixedByName = {}
cfxMX.allHeloByName = {}
cfxMX.allGroundByName = {}
cfxMX.allSeaByName = {}
cfxMX.allStaticByName ={}
function cfxMX.getGroupFromDCSbyName(aName, fetchOriginal)
if not fetchOriginal then fetchOriginal = false end
@ -154,7 +164,7 @@ function cfxMX.getStaticFromDCSbyName(aName, fetchOriginal)
return nil, "<none>", "<none>", "<no group name>"
end
function cfxMX.createCrossReference()
function cfxMX.createCrossReferences()
for coa_name_miz, coa_data in pairs(env.mission.coalition) do -- iterate all coalitions
local coa_name = coa_name_miz
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 == "plane" 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)
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!
@ -188,6 +198,21 @@ function cfxMX.createCrossReference()
cfxMX.groupNamesByID[aID] = aName
cfxMX.groupIDbyName[aName] = aID
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 --if has category data
end --if plane, helo etc... category
@ -212,8 +237,10 @@ function cfxMX.catText2ID(inText)
end
function cfxMX.start()
cfxMX.createCrossReference()
trigger.action.outText("cfxMX: "..#cfxMX.groupNamesByID .. " groups processed successfully", 30)
cfxMX.createCrossReferences()
if cfxMX.verbose then
trigger.action.outText("cfxMX: "..#cfxMX.groupNamesByID .. " groups processed successfully", 30)
end
end
-- start

View File

@ -1,5 +1,5 @@
cfxObjectSpawnZones = {}
cfxObjectSpawnZones.version = "1.2.1"
cfxObjectSpawnZones.version = "1.3.0"
cfxObjectSpawnZones.requiredLibs = {
"dcsCommon", -- common is of course needed for everything
-- pretty stupid to check for this since we
@ -26,7 +26,10 @@ cfxObjectSpawnZones.verbose = false
-- 1.1.5 - spawn?, spawnObjects? synonyms
-- 1.2.0 - DML flag upgrade
-- 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
cfxObjectSpawnZones.allSpawners = {}
@ -58,13 +61,9 @@ function cfxObjectSpawnZones.createSpawner(inZone)
-- connect with ME if a trigger flag is given
if cfxZones.hasProperty(inZone, "f?") then
theSpawner.triggerFlag = cfxZones.getStringFromZoneProperty(inZone, "f?", "none")
end
if cfxZones.hasProperty(inZone, "spawn?") then
elseif cfxZones.hasProperty(inZone, "spawn?") then
theSpawner.triggerFlag = cfxZones.getStringFromZoneProperty(inZone, "spawn?", "none")
end
if cfxZones.hasProperty(inZone, "spawnObjects?") then
elseif cfxZones.hasProperty(inZone, "spawnObjects?") then
theSpawner.triggerFlag = cfxZones.getStringFromZoneProperty(inZone, "spawnObjects?", "none")
end
@ -116,6 +115,11 @@ function cfxObjectSpawnZones.createSpawner(inZone)
theSpawner.requestable = cfxZones.getBoolFromZoneProperty(inZone, "requestable", false)
if theSpawner.requestable then theSpawner.paused = true end
-- see if the spawn can be made brittle/delicte
if cfxZones.hasProperty(inZone, "useDelicates") then
theSpawner.delicateName = cfxZones.getStringFromZoneProperty(inZone, "useDelicates", "<none>")
end
-- see if it is linked to a ship to set realtive orig headiong
if inZone.linkedUnit then
@ -258,6 +262,16 @@ function cfxObjectSpawnZones.spawnObjectNTimes(aSpawner, theType, n, container)
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
end
@ -302,6 +316,17 @@ function cfxObjectSpawnZones.spawnObjectNTimes(aSpawner, theType, n, container)
cfxCargoManager.addCargo(theObject)
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
currDegree = currDegree + degreeIncrement
end

View File

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

View File

@ -1,5 +1,5 @@
dcsCommon = {}
dcsCommon.version = "2.6.6"
dcsCommon.version = "2.6.8"
--[[-- VERSION HISTORY
2.2.6 - compassPositionOfARelativeToB
- clockPositionOfARelativeToB
@ -82,7 +82,9 @@ dcsCommon.version = "2.6.6"
- new stringRemainsStartingWith()
- new stripLF()
- new removeBlanks()
2.6.7 - new menu2text()
2.6.8 - new getMissionName()
- new flagArrayFromString()
--]]--
-- dcsCommon is a library of common lua functions
@ -1882,6 +1884,21 @@ dcsCommon.version = "2.6.6"
return catNum
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
function dcsCommon.dumpVar(key, value, prefix, inrecursion)
if not inrecursion then
@ -2361,6 +2378,68 @@ function dcsCommon.latLon2Text(lat, lon)
return lat, lon
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

View File

@ -1,5 +1,5 @@
delicates = {}
delicates.version = "1.0.0"
delicates.version = "1.1.0"
delicates.verbose = false
delicates.ups = 1
delicates.requiredLibs = {
@ -12,6 +12,11 @@ delicates.inventory = {}
--[[--
Version History
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)
@ -68,7 +73,7 @@ function delicates.makeZoneInventory(theZone)
for idy, anObject in pairs(collector) do
local oName = anObject:getName()
if type(oName) == 'number' then oName = tostring(oName) end
local oLife = anObject:getLife()
local oLife = anObject:getLife() - anObject:getLife() * theZone.safetyMargin
if theZone.verbose or delicates.verbose then
trigger.action.outText("+++deli: cat=".. aCat .. ":<" .. oName .. "> Life=" .. oLife, 30)
end
@ -91,25 +96,47 @@ function delicates.makeZoneInventory(theZone)
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)
theZone.power = cfxZones.getNumberFromZoneProperty(theZone, "power", 10)
if cfxZones.hasProperty(theZone, "delicatesHit!") then
theZone.delicateHit = cfxZones.getStringFromZoneProperty(theZone, "delicatesHit!", "*<none>")
end
if cfxZones.hasProperty(theZone, "f!") then
elseif cfxZones.hasProperty(theZone, "f!") then
theZone.delicateHit = cfxZones.getStringFromZoneProperty(theZone, "f!", "*<none>")
end
if cfxZones.hasProperty(theZone, "out!") then
elseif cfxZones.hasProperty(theZone, "out!") then
theZone.delicateHit = cfxZones.getStringFromZoneProperty(theZone, "out!", "*<none>")
end
-- safety margin
theZone.safetyMargin = cfxZones.getNumberFromZoneProperty(theZone, "safetyMargin", 0)
-- DML Method
theZone.delicateHitMethod = cfxZones.getStringFromZoneProperty(theZone, "method", "inc")
if cfxZones.hasProperty(theZone, "delicateMethod") then
theZone.delicateHitMethod = cfxZones.getStringFromZoneProperty(theZone, "delicatesMethod", "inc")
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)
@ -117,6 +144,11 @@ function delicates.createDelicatesWithZone(theZone)
-- may want to filter by objects, can be passed in delicates
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
trigger.action.outText("+++deli: new delicates zone <".. theZone.name ..">", 30)
end
@ -185,14 +217,42 @@ function delicates:onEvent(theEvent)
local oName = theObj:getName()
local desc = delicates.inventory[oName]
if desc then
-- trigger.action.outText("+++deli: REGISTERED HIT -- removing!", 30)
delicates.blowUpObject(desc)
-- remove it from further searches
delicates.inventory[oName] = nil
-- see if damage exceeds maximum
local cLife = theObj:getLife()
if cLife < desc.oLife then
if desc.theZone.verbose or delicates.verbose then
trigger.action.outText("+++deli: BRITTLE TRIGGER: life <" .. cLife .. "> below safety margin <" .. oDesc.oLife .. ">", 30)
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
-- 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
--
@ -228,12 +288,12 @@ function delicates.update()
if theObj then
local cLife = theObj:getLife()
if cLife >= oDesc.oLife then
-- transfer to next iter
-- all well, transfer to next iter
newInventory[oName] = oDesc
else
-- blow stuff up
-- health beneath min. blow stuff up
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
delicates.blowUpObject(oDesc)
end
@ -245,6 +305,13 @@ function delicates.update()
end
end
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
--

View File

@ -566,5 +566,5 @@ end
To do
- reset? flag: will reset all to MX locationS
- 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.version = "1.0.1"
radioMenu.version = "1.1.0"
radioMenu.verbose = false
radioMenu.ups = 1
radioMenu.requiredLibs = {
@ -12,6 +12,9 @@ radioMenu.menus = {}
Version History
1.0.0 Initial version
1.0.1 spelling corrections
1.1.0 removeMenu
addMenu
menuVisible
--]]--
function radioMenu.addRadioMenu(theZone)
@ -32,17 +35,60 @@ end
--
-- 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)
local rootName = cfxZones.getStringFromZoneProperty(theZone, "radioMenu", "<No Name>")
theZone.rootName = cfxZones.getStringFromZoneProperty(theZone, "radioMenu", "<No Name>")
theZone.coalition = cfxZones.getCoalitionFromZoneProperty(theZone, "coalition", 0)
if theZone.coalition == 0 then
theZone.rootMenu = missionCommands.addSubMenu(rootName, nil)
else
theZone.rootMenu = missionCommands.addSubMenuForCoalition(theZone.coalition, rootName, nil)
end
theZone.menuVisible = cfxZones.getBoolFromZoneProperty(theZone, "menuVisible", true)
-- install menu if not hidden
if theZone.menuVisible then
radioMenu.installMenu(theZone)
end
--[[--
-- now do the two options
local menuA = cfxZones.getStringFromZoneProperty(theZone, "itemA", "<no A submenu>")
if theZone.coalition == 0 then
@ -78,6 +124,7 @@ function radioMenu.createRadioMenuWithZone(theZone)
end
end
--]]--
-- get the triggers & methods here
theZone.radioMethod = cfxZones.getStringFromZoneProperty(theZone, "method", "inc")
@ -85,6 +132,8 @@ function radioMenu.createRadioMenuWithZone(theZone)
theZone.radioMethod = cfxZones.getStringFromZoneProperty(theZone, "radioMethod", "inc")
end
theZone.radioTriggerMethod = cfxZones.getStringFromZoneProperty(theZone, "radioTriggerMethod", "change")
theZone.itemAChosen = cfxZones.getStringFromZoneProperty(theZone, "A!", "*<none>")
theZone.cooldownA = cfxZones.getNumberFromZoneProperty(theZone, "cooldownA", 0)
theZone.mcdA = 0
@ -105,6 +154,16 @@ function radioMenu.createRadioMenuWithZone(theZone)
theZone.mcdD = 0
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
trigger.action.outText("+++radioMenu: new radioMenu zone <".. theZone.name ..">", 30)
end
@ -189,13 +248,44 @@ end
--
-- Update -- required when we can enable/disable a zone's menu
--
--[[--
function radioMenu.update()
-- call me in a second to poll triggers
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
--]]--
--
-- Config & Start
@ -238,7 +328,7 @@ function radioMenu.start()
end
-- start update
--radioMenu.update()
radioMenu.update()
trigger.action.outText("cfx radioMenu v" .. radioMenu.version .. " started.", 30)
return true
@ -251,7 +341,5 @@ if not radioMenu.start() then
end
--[[--
to do: turn on/off via flags
callbacks for the menus
one-shot items
--]]--

View File

@ -1,13 +1,15 @@
-- theDebugger
debugger = {}
debugger.version = "1.0.1"
debugger.version = "1.1.1"
debugDemon = {}
debugDemon.version = "1.0.0"
debugDemon.version = "1.1.1"
debugger.verbose = false
debugger.ups = 4 -- every 0.25 second
debugger.name = "DML Debugger" -- for compliance with cfxZones
debugger.log = ""
--[[--
Version History
1.0.0 - Initial version
@ -15,14 +17,67 @@ debugger.name = "DML Debugger" -- for compliance with cfxZones
- changed 'on' to 'active' in config zone
- merged debugger and debugDemon
- 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 = {
"dcsCommon", -- always
"cfxZones", -- Zones, of course
}
-- note: saving logs requires persistence module
-- will auto-abort saving if not present
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)
table.insert(debugger.debugZones, theZone)
@ -33,7 +88,7 @@ function debugger.getDebuggerByName(aName)
if aName == aZone.name then return aZone end
end
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
return nil
@ -65,7 +120,7 @@ function debugger.createDebuggerWithZone(theZone)
-- say who we are and what we are monitoring
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
-- read main debug array
@ -77,7 +132,7 @@ function debugger.createDebuggerWithZone(theZone)
for idx, aFlag in pairs(flagArray) do
local fVal = cfxZones.getFlagValue(aFlag, theZone)
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
valueArray[aFlag] = fVal
end
@ -226,7 +281,7 @@ function debugger.debugZone(theZone)
-- generate the ouput message
local msg = theZone.debugMsg
msg = debugger.processDebugMsg(msg, theZone, aFlag, oldVal, newValue)
trigger.action.outText(msg, 30)
debugger.outText(msg, 30)
end
end
@ -239,7 +294,7 @@ function debugger.resetObserver(theZone)
for idf, aFlag in pairs(theZone.flagArray) do
local fVal = cfxZones.getFlagValue(aFlag, theZone)
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
theZone.valueArray[aFlag] = fVal
end
@ -256,39 +311,39 @@ function debugger.showObserverState(theZone)
for idf, aFlag in pairs(theZone.flagArray) do
local fVal = cfxZones.getFlagValue(aFlag, theZone)
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
theZone.valueArray[aFlag] = fVal
end
end
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
-- show this zone's state
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)
else
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
trigger.action.outText("---debug: end of state --- ", 30)
debugger.outText("---debug: end of state --- ", 30)
end
function debugger.doActivate()
debugger.active = true
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
function debugger.doDeactivate()
debugger.active = false
if debugger.verbose or true then
trigger.action.outText("+++ debugger deactivated", 30)
debugger.outText("+++ debugger deactivated", 30)
end
end
@ -339,7 +394,7 @@ function debugger.readConfigZone()
local theZone = cfxZones.getZoneByName("debuggerConfig")
if not theZone then
if debugger.verbose then
trigger.action.outText("+++debug: NO config zone!", 30)
debugger.outText("+++debug: NO config zone!", 30)
end
theZone = cfxZones.createSimpleZone("debuggerConfig")
end
@ -371,7 +426,7 @@ function debugger.readConfigZone()
debugger.ups = cfxZones.getNumberFromZoneProperty(theZone, "ups", 4)
if debugger.verbose then
trigger.action.outText("+++debug: read config", 30)
debugger.outText("+++debug: read config", 30)
end
end
@ -398,22 +453,22 @@ function debugger.start()
local attrZones = cfxZones.getZonesWithAttributeNamed("debug")
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
-- say if we are active
if debugger.verbose then
if debugger.active then
trigger.action.outText("+++debugger loaded and active", 30)
debugger.outText("+++debugger loaded and active", 30)
else
trigger.action.outText("+++ debugger: standing by for activation", 30)
debugger.outText("+++ debugger: standing by for activation", 30)
end
end
-- start update
debugger.update()
trigger.action.outText("cfx debugger v" .. debugger.version .. " started.", 30)
debugger.outText("cfx debugger v" .. debugger.version .. " started.", 30)
return true
end
@ -441,6 +496,7 @@ debugDemon.verbose = false
--[[--
Version History
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)
return success
else
trigger.action.outText("***error: unknown command <".. cmd .. ">", 30)
debugger.outText("***error: unknown command <".. cmd .. ">", 30)
return false
end
@ -592,7 +648,7 @@ end
-- COMMANDS
--
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 .. "set <flagname> <number> -- set flag to value <number>" ..
"\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 " .. 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)
return true
end
@ -622,14 +680,14 @@ function debugDemon.processNewCommand(args, event)
-- syntax new <observername> [[for] <condition>]
local observerName = args[1]
if not observerName then
trigger.action.outText("*** new: missing observer name.", 30)
debugger.outText("*** new: missing observer name.", 30)
return false -- allows correction
end
-- see if this observer already existst
local theObserver = debugger.getDebuggerByName(observerName)
if theObserver then
trigger.action.outText("*** new: observer <" .. observerName .. "> already exists.", 30)
debugger.outText("*** new: observer <" .. observerName .. "> already exists.", 30)
return false -- allows correction
end
@ -638,7 +696,7 @@ function debugDemon.processNewCommand(args, event)
local remainderName = event.remainder
local rObserver = debugger.getDebuggerByName(remainderName)
if rObserver then
trigger.action.outText("*** new: observer <" .. remainderName .. "> already exists.", 30)
debugger.outText("*** new: observer <" .. remainderName .. "> already exists.", 30)
return false -- allows correction
end
@ -655,14 +713,14 @@ function debugDemon.processNewCommand(args, event)
if condition == "for" then condition = args[3] end
if condition 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
end
theZone.debugInputMethod = condition
end
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
end
@ -670,14 +728,14 @@ function debugDemon.processUpdateCommand(args, event)
-- syntax update <observername> [[to] <condition>]
local observerName = args[1]
if not observerName then
trigger.action.outText("*** update: missing observer name.", 30)
debugger.outText("*** update: missing observer name.", 30)
return false -- allows correction
end
-- see if this observer already existst
local theZone = debugger.getDebuggerByName(observerName)
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
end
@ -685,13 +743,13 @@ function debugDemon.processUpdateCommand(args, event)
if condition == "to" then condition = args[3] end
if condition 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
end
theZone.debugInputMethod = condition
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
end
@ -699,21 +757,21 @@ function debugDemon.processDropCommand(args, event)
-- syntax drop <observername>
local observerName = event.remainder -- remainder
if not observerName then
trigger.action.outText("*** drop: missing observer name.", 30)
debugger.outText("*** drop: missing observer name.", 30)
return false -- allows correction
end
-- see if this observer already existst
local theZone = debugger.getDebuggerByName(observerName)
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
end
-- now simply and irrevocable remove the observer, unless it's home,
-- in which case it's simply reset
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
-- we've seen stranger things
return false -- allows correction
@ -721,7 +779,7 @@ function debugDemon.processDropCommand(args, event)
debugger.removeDebugger(theZone)
trigger.action.outText("*** [" .. dcsCommon.nowString() .. "] debugger: dropped observer <" .. observerName .. ">", 30)
debugger.outText("*** [" .. dcsCommon.nowString() .. "] debugger: dropped observer <" .. observerName .. ">", 30)
return true
end
-- 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
local flagName = args[1]
if not flagName then
trigger.action.outText("*** observe: missing flag name.", 30)
debugger.outText("*** observe: missing flag name.", 30)
return false -- allows correction
end
@ -738,7 +796,7 @@ function debugDemon.processObserveCommand(args, event)
if args[2] == "with" then
local aName = args[3]
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
end
aName = dcsCommon.stringRemainsStartingWith(event.remainder, aName)
@ -746,12 +804,12 @@ function debugDemon.processObserveCommand(args, event)
if not withTracker then
-- withTracker = debugDemon.createObserver(aName)
-- debugger.addDebugger(withTracker)
trigger.action.outText("*** observe: no observer <" .. aName .. "> exists", 30)
debugger.outText("*** observe: no observer <" .. aName .. "> exists", 30)
return false -- allows correction
end
else -- not with as arg 2
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
end
-- use own observer
@ -759,13 +817,13 @@ function debugDemon.processObserveCommand(args, event)
end
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
end
-- we add flag to tracker and init value
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
end
@ -774,7 +832,7 @@ function debugDemon.processShowCommand(args, event)
-- observer has precendce over flag
local theName = args[1]
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
end
@ -785,12 +843,12 @@ function debugDemon.processShowCommand(args, event)
if not theObserver then
-- we directly use trigger.misc
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
end
-- 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 values = theObserver.valueArray
for idx, flagName in pairs(flags) do
@ -804,7 +862,7 @@ function debugDemon.processShowCommand(args, event)
theMark = " ! "
trailer = ", HIT!"
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
return true
@ -836,7 +894,7 @@ function debugDemon.processSnapCommand(args, event)
theName = dcsCommon.stringRemainsStartingWith(event.remainder, theName)
theObserver = debugger.getDebuggerByName(theName)
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
end
end
@ -861,26 +919,26 @@ function debugDemon.processSnapCommand(args, event)
local sz = dcsCommon.getSizeOfTable(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
end
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
local cVal = trigger.misc.getUserFlag(flagName)
local mark = ' '
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
trigger.action.outText("*** END", 30)
debugger.outText("*** END", 30)
return true
end
function debugDemon.processNoteCommand(args, event)
local n = event.remainder
trigger.action.outText("*** [" .. dcsCommon.nowString() .. "]: " .. n, 30)
debugger.outText("*** [" .. dcsCommon.nowString() .. "]: " .. n, 30)
return true
end
@ -888,7 +946,7 @@ function debugDemon.processSetCommand(args, event)
-- syntax set <flagname> <value>
local theName = args[1]
if not theName then
trigger.action.outText("*** set: missing flag name.", 30)
debugger.outText("*** set: missing flag name.", 30)
return false -- allows correction
end
@ -900,13 +958,21 @@ function debugDemon.processSetCommand(args, event)
end
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
end
-- we set directly, no cfxZones procing
trigger.action.outText("*** [" .. dcsCommon.nowString() .. "] debug: set flag <" .. theName .. "> to <" .. theVal .. ">", 30)
theVal = tonumber(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
end
@ -914,7 +980,7 @@ function debugDemon.processIncCommand(args, event)
-- syntax inc <flagname>
local theName = args[1]
if not theName then
trigger.action.outText("*** inc: missing flag name.", 30)
debugger.outText("*** inc: missing flag name.", 30)
return false -- allows correction
end
@ -922,7 +988,7 @@ function debugDemon.processIncCommand(args, event)
local nVal = cVal + 1
-- 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)
return true
end
@ -931,7 +997,7 @@ function debugDemon.processFlipCommand(args, event)
-- syntax flip <flagname>
local theName = args[1]
if not theName then
trigger.action.outText("*** flip: missing flag name.", 30)
debugger.outText("*** flip: missing flag name.", 30)
return false -- allows correction
end
@ -939,7 +1005,7 @@ function debugDemon.processFlipCommand(args, event)
if cVal == 0 then nVal = 1 else nVal = 0 end
-- 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)
return true
end
@ -952,9 +1018,9 @@ function debugDemon.processListCommand(args, event)
prefix = event.remainder -- dcsCommon.stringRemainsStartingWith(event.text, prefix)
end
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
trigger.action.outText("*** [" .. dcsCommon.nowString() .. "] listing all observers:", 30)
debugger.outText("*** [" .. dcsCommon.nowString() .. "] listing all observers:", 30)
end
local allObservers = debugger.debugZones
@ -966,7 +1032,7 @@ function debugDemon.processListCommand(args, event)
end
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
return true
@ -976,20 +1042,20 @@ function debugDemon.processWhoCommand(args, event)
-- syntax: who <flagname>
local flagName = event.remainder -- args[1]
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
end
local observers = debugger.isObserving(flagName)
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
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
trigger.action.outText(" <" .. theZone.name .. "> looking for <value " .. theZone.debugInputMethod .. ">", 30)
debugger.outText(" <" .. theZone.name .. "> looking for <value " .. theZone.debugInputMethod .. ">", 30)
end
return true
@ -1001,7 +1067,7 @@ function debugDemon.processForgetCommand(args, event)
local flagName = args[1]
if not flagName then
trigger.action.outText("*** forget: missing flag name.", 30)
debugger.outText("*** forget: missing flag name.", 30)
return false -- allows correction
end
@ -1009,19 +1075,19 @@ function debugDemon.processForgetCommand(args, event)
if args[2] == "with" or args[2] == "from" then -- we also allow 'from'
local aName = args[3]
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
end
aName = dcsCommon.stringRemainsStartingWith(event.remainder, aName)
withTracker = debugger.getDebuggerByName(aName)
if not withTracker then
trigger.action.outText("*** forget: no observer named <" .. aName .. ">", 30)
debugger.outText("*** forget: no observer named <" .. aName .. ">", 30)
return false
end
else -- not with as arg 2
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
end
-- use own observer
@ -1029,13 +1095,13 @@ function debugDemon.processForgetCommand(args, event)
end
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
end
-- we add flag to tracker and init value
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
end
@ -1057,23 +1123,35 @@ function debugDemon.processResetCommand(args, event)
local obsName = args[1]
if not obsName then
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
end
local withTracker = nil
--if args[2] == "with" then
local aName = args[1]
aName = event.remainder -- dcsCommon.stringRemainsStartingWith(event.text, aName)
local aName = event.remainder
withTracker = debugger.getDebuggerByName(aName)
if not withTracker then
trigger.action.outText("*** reset: no observer <" .. aName .. ">", 30)
debugger.outText("*** reset: no observer <" .. aName .. ">", 30)
return false
end
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
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
--
@ -1084,7 +1162,7 @@ function debugDemon.readConfigZone()
local theZone = cfxZones.getZoneByName("debugDemonConfig")
if not theZone then
if debugDemon.verbose then
trigger.action.outText("+++debug: NO config zone!", 30)
debugger.outText("+++debug (daemon): NO config zone!", 30)
end
theZone = cfxZones.createSimpleZone("debugDemonConfig")
end
@ -1099,7 +1177,7 @@ function debugDemon.readConfigZone()
if debugger.verbose then
trigger.action.outText("+++debug (deamon): read config", 30)
debugger.outText("+++debug (deamon): read config", 30)
end
end
@ -1140,6 +1218,8 @@ function debugDemon.init()
debugDemon.addCommndProcessor("stop", debugDemon.processStopCommand)
debugDemon.addCommndProcessor("reset", debugDemon.processResetCommand)
debugDemon.addCommndProcessor("save", debugDemon.processSaveCommand)
debugDemon.addCommndProcessor("?", debugDemon.processHelpCommand)
debugDemon.addCommndProcessor("help", debugDemon.processHelpCommand)
@ -1159,7 +1239,11 @@ function debugDemon.start()
debugDemon.snapshot = debugDemon.createSnapshot(debugger.debugZones)
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
if debugDemon.init() then
@ -1170,6 +1254,11 @@ else
end
--[[--
- track units/groups: health changes
- track players: unit change
- track units/groups/objects: health changes
- 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.