Version 2.3.4

Tickle, json persistence bug for cyclic structures
This commit is contained in:
Christian Franz
2024-10-10 08:30:18 +02:00
parent a2e8ead577
commit a70fadc550
10 changed files with 501 additions and 56 deletions

View File

@@ -1,5 +1,5 @@
cfxMX = {}
cfxMX.version = "2.1.0"
cfxMX.version = "2.2.0"
cfxMX.verbose = false
--[[--
Mission data decoder. Access to ME-built mission structures
@@ -18,11 +18,13 @@ cfxMX.verbose = false
- new isDynamicPlayer()
- new isMEPlayer()
- new isMEPlayerGroup()
2.2.0 - new groupCatByName[]
--]]--
cfxMX.groupNamesByID = {}
cfxMX.groupIDbyName = {}
cfxMX.unitIDbyName = {}
cfxMX.groupCatByName = {}
cfxMX.groupDataByName = {}
cfxMX.groupTypeByName = {} -- category of group: "helicopter", "plane", "ship"...
cfxMX.groupCoalitionByName = {}
@@ -226,7 +228,6 @@ function cfxMX.createCrossReferences()
category = "train"
obj_type_name = "train"
end
cfxMX.groupTypeByName[aName] = category
cfxMX.groupNamesByID[aID] = aName
cfxMX.groupIDbyName[aName] = aID
@@ -237,16 +238,22 @@ function cfxMX.createCrossReferences()
-- now make the type-specific xrefs
if obj_type_name == "helicopter" then
cfxMX.allHeloByName[aName] = group_data
cfxMX.groupCatByName[aName] = 1
elseif obj_type_name == "ship" then
cfxMX.allSeaByName[aName] = group_data
cfxMX.groupCatByName[aName] = 3
elseif obj_type_name == "plane" then
cfxMX.allFixedByName[aName] = group_data
cfxMX.groupCatByName[aName] = 0
elseif obj_type_name == "vehicle" then
cfxMX.allGroundByName[aName] = group_data
cfxMX.groupCatByName[aName] = 2
elseif obj_type_name == "static" then
cfxMX.allStaticByName[aName] = group_data
-- cfxMX.groupCatByName[aName] = -1 -- not covered
elseif obj_type_name == "train" then
cfxMX.allTrainsByName[aName] = group_data
cfxMX.groupCatByName[aName] = 4
else
-- should be impossible, but still
trigger.action.outText("+++MX: <" .. obj_type_name .. "> unknown type for <" .. aName .. ">", 30)

View File

@@ -953,6 +953,9 @@ function cfxGroundTroops.addGroundTroopsToPool(troops) -- troops MUST be a table
end
if not troops.orders then troops.orders = "guard" end
troops.orders = troops.orders:lower()
--trigger.action.outText("Enter adding group <" .. troops.name .. "> with orders=<" .. troops.orders .. "> to pool", 30)
if not troops.moveFormation then troops.moveFormation = "Custom" end
troops.reschedule = true -- in case we use scheduled update
-- we now add to internal array. this is worked on by all

View File

@@ -1,5 +1,5 @@
cfxHeloTroops = {}
cfxHeloTroops.version = "3.1.2"
cfxHeloTroops.version = "3.1.3"
cfxHeloTroops.verbose = false
cfxHeloTroops.autoDrop = true
cfxHeloTroops.autoPickup = false
@@ -20,6 +20,10 @@ cfxHeloTroops.requestRange = 500 -- meters
3.1.0 - compatible with DCS 2.9.6 dynamic spawning
3.1.1 - deployTroopsFromHelicopter() captureandhold
3.1.2 - doLoadGroup - test if group is still alive edge case handling
3.1.3 - decycled structures (destination zone) on save
- upcycled structures (destination) on load
- loadSound and disembarkSound
--]]--
@@ -658,7 +662,7 @@ function cfxHeloTroops.deployTroopsFromHelicopter(conf)
troop.destination = dest -- transfer target zone for attackzone oders
cfxGroundTroops.addGroundTroopsToPool(troop) -- will schedule move orders
trigger.action.outTextForGroup(conf.id, "<" .. theGroup:getName() .. "> have deployed to the ground with orders " .. orders .. "!", 30)
trigger.action.outSoundForGroup(conf.id, cfxHeloTroops.actionSound)
trigger.action.outSoundForGroup(conf.id, cfxHeloTroops.disembarkSound)
-- see if this is tracked by a tracker, and pass them back so
-- they can un-limbo
if groupTracker then
@@ -747,7 +751,7 @@ function cfxHeloTroops.doLoadGroup(args)
-- say so
trigger.action.outTextForGroup(conf.id, "Team '".. conf.troopsOnBoard.name .."' aboard, ready to go!", 30)
trigger.action.outSoundForGroup(conf.id, cfxHeloTroops.actionSound) -- "Quest Snare 3.wav")
trigger.action.outSoundForGroup(conf.id, cfxHeloTroops.loadSound) -- "Quest Snare 3.wav")
-- reset menu
cfxHeloTroops.removeComms(conf.unit)
@@ -898,6 +902,8 @@ function cfxHeloTroops.readConfigZone()
cfxHeloTroops.combatDropScore = theZone:getNumberFromZoneProperty( "combatDropScore", 200)
cfxHeloTroops.actionSound = theZone:getStringFromZoneProperty("actionSound", "Quest Snare 3.wav")
cfxHeloTroops.loadSound = theZone:getStringFromZoneProperty("loadSound", cfxHeloTroops.actionSound)
cfxHeloTroops.disembarkSound = theZone:getStringFromZoneProperty("disembarkSound", cfxHeloTroops.actionSound)
cfxHeloTroops.requestRange = theZone:getNumberFromZoneProperty("requestRange", 500)
-- add own troop carriers
@@ -920,6 +926,10 @@ function cfxHeloTroops.saveData()
for gName, gData in pairs(cfxHeloTroops.deployedTroops) do
local sData = dcsCommon.clone(gData)
dcsCommon.synchGroupData(sData.groupData)
if sData.destination then
net.log("cfxHeloTroops: decycling troop 'destination' for <" .. sData.destination:getName() .. ">")
sData.destination = sData.destination:getName()
end
allTroopData[gName] = sData
end
theData.troops = allTroopData
@@ -947,6 +957,13 @@ function cfxHeloTroops.loadData()
local range = gdTroop.range
local cty = gData.cty
local cat = gData.cat
local dest = nil
-- synch destination from name to real zone
if gdTroop.destination then
dest = cfxZones.getZoneByName(gdTroop.destination)
net.log("cfxHeloTroops: attempting to restore troop destination zone <" .. gdTroop.destination .. ">")
end
-- now spawn, but first
-- add to my own deployed queue so we can save later
@@ -957,6 +974,7 @@ function cfxHeloTroops.loadData()
-- add to groundTroops
local newTroops = cfxGroundTroops.createGroundTroops(theGroup, range, orders)
newTroops.destination = dest
cfxGroundTroops.addGroundTroopsToPool(newTroops)
end
end

View File

@@ -1,6 +1,6 @@
impostors={}
impostors.version = "1.0.1"
impostors.version = "1.1.0"
impostors.verbose = false
impostors.ups = 1
impostors.requiredLibs = {
@@ -18,7 +18,9 @@ impostors.uniqueCounter = 8200000 -- clones start at 9200000
Version History
1.0.0 - initial version
1.0.1 - added some verbosity
1.1.0 - filtered dead units during spawns
cleanup
some performance boost for mx lookup
LIMITATIONS:
must be on ground (or would be very silly
@@ -41,11 +43,9 @@ function impostors.getCloneZoneByName(aName)
if impostors.verbose then
trigger.action.outText("+++ipst: no impostor with name <" .. aName ..">", 30)
end
return nil
end
--
-- spawn impostors from data
--
@@ -110,7 +110,9 @@ function impostors.getRawDataFromGroupNamed(gName)
local cat = theGroup:getCategory()
-- access mxdata for livery because getDesc does not return the livery
local liveries = {}
local mxData = cfxMX.getGroupFromDCSbyName(gName)
-- local mxData = cfxMX.getGroupFromDCSbyName(gName)
local mxData = cfxMX.groupDataByName[gName] -- performance
if mxData then mxData = dcsCommon.clone(mxData) end
for idx, theUnit in pairs (mxData.units) do
liveries[theUnit.name] = theUnit.livery_id
end
@@ -136,8 +138,9 @@ function impostors.getRawDataFromGroupNamed(gName)
ir.y = up.z -- !!! warning!
-- see if any zones are linked to this unit
ir.linkedZones = cfxZones.zonesLinkedToUnit(theUnit)
table.insert(rawUnits, ir)
if theUnit:getLife() > 1 then
table.insert(rawUnits, ir)
end
ctry = theUnit:getCountry()
end
rawGroup.ctry = ctry
@@ -161,21 +164,15 @@ function impostors.createImpostorWithZone(theZone) -- has "impostor?"
theZone.reanimateFlag = cfxZones.getStringFromZoneProperty(theZone, "reanimate?", "*<none>")
theZone.lastReanimateValue = cfxZones.getFlagValue(theZone.reanimateFlag, theZone)
end
-- watchflag:
-- triggerMethod
theZone.impostorTriggerMethod = cfxZones.getStringFromZoneProperty(theZone, "triggerMethod", "change")
if cfxZones.hasProperty(theZone, "impostorTriggerMethod") then
theZone.impostorTriggerMethod = cfxZones.getStringFromZoneProperty(theZone, "impostorTriggerMethod", "change")
end
-- local localGroups = impostors.allGroupsInZoneByData(theZone)
theZone.groupNames = cfxZones.allGroupNamesInZone(theZone)
theZone.impostor = false -- we have not yet turned units into impostors
theZone.myImpostors = {}
theZone.origin = cfxZones.getPoint(theZone) -- save reference point for all groupVectors
theZone.onStart = cfxZones.getBoolFromZoneProperty(theZone, "onStart", false)
-- blinking
@@ -207,7 +204,6 @@ function impostors.createImpostorWithZone(theZone) -- has "impostor?"
-- we end with group replaced by impostors
end
--
-- Spawning
--
@@ -230,6 +226,9 @@ function impostors.turnGroupsIntoImpostors(theZone)
end
local aGroup = Group.getByName(gName)
if aGroup and gName then
if theZone.verbose then
trigger.action.outText("impostoring group <" .. gName .. ">", 30)
end
-- record unit data to create impostors
local rawData, cat, ctry = impostors.getRawDataFromGroupNamed(gName)
-- if we are tracking the group, remove it from tracker
@@ -241,8 +240,6 @@ function impostors.turnGroupsIntoImpostors(theZone)
-- we may do some book-keeping first for the
-- names. we'll see later
Group.destroy(aGroup)
-- local rawData, cat, ctry = cfxMX.getGroupFromDCSbyName(gName)
-- local origID = rawData.groupId -- may be redundant
-- now spawn impostors based on the rawData,
-- and return impostorGroup
local impostorGroup = impostors.spawnImpostorsFromData(rawData, cat, ctry)
@@ -303,28 +300,40 @@ function impostors.spawnGroupsFromImpostor(theZone)
for idx, groupName in pairs(theZone.groupNames) do
-- get my group data from MX based on my name
-- we get from MX so we get all path and order info
local rawData, cat, ctry = cfxMX.getGroupFromDCSbyName(groupName)
-- local rawData, cat, ctry = cfxMX.getGroupFromDCSbyName(groupName)
local rawData = cfxMX.groupDataByName[groupName]
if rawData then rawData = dcsCommon.clone(rawData) end
local cat = cfxMX.groupCatByName[groupName]
local ctry = cfxMX.countryByName[groupName]
local impostorGroup = theZone.myImpostors[groupName]
local relinkZones = {}
-- now iterate all units in that group, and remove their impostors
for idy, theUnit in pairs(rawData.units) do
local impName = impostorGroup[theUnit.name]
local impStat = StaticObject.getByName(impName)
if impStat and impStat:isExist() and impStat:getLife() > 1 then
-- still alive. read x, y and heading
local sp = impStat:getPoint()
theUnit.x = sp.x
theUnit.y = sp.z -- !!!
theUnit.heading = dcsCommon.getUnitHeading(impStat) -- should also work for statics
-- should automatically handle ["livery_id"]
relinkZones[theUnit.name] = cfxZones.zonesLinkedToUnit(impStat)
else
-- dead
table.insert(deadUnits, theUnit.name)
end
-- destroy imp
if impStat and impStat:isExist() then
impStat:destroy()
if theUnit and theUnit.name then
local impName = impostorGroup[theUnit.name]
if not impName then
if theZone.verbose then
trigger.action.outText("group <" .. groupName .. ">: no impostor for <" .. theUnit.name .. ">", 30)
end
else
local impStat = StaticObject.getByName(impName)
if impStat and impStat:isExist() and impStat:getLife() > 1 then
-- still alive. read x, y and heading
local sp = impStat:getPoint()
theUnit.x = sp.x
theUnit.y = sp.z -- !!!
theUnit.heading = dcsCommon.getUnitHeading(impStat) -- should also work for statics
-- should automatically handle ["livery_id"]
relinkZones[theUnit.name] = cfxZones.zonesLinkedToUnit(impStat)
else
-- dead
table.insert(deadUnits, theUnit.name)
end
-- destroy imp
if impStat and impStat:isExist() then
impStat:destroy()
end
end
end
end
@@ -335,7 +344,8 @@ function impostors.spawnGroupsFromImpostor(theZone)
-- now create the group
if theZone.blinkTime <= 0 then
-- immediate spawn
local newGroup = coalition.addGroup(ctry, cfxMX.catText2ID(cat), rawData)
--local newGroup = coalition.addGroup(ctry, cfxMX.catText2ID(cat), rawData)
local newGroup = coalition.addGroup(ctry, cat, rawData)
impostors.relinkZonesForGroup(relinkZones, newGroup)
if theZone.trackWith and groupTracker.addGroupToTrackerNamed then
-- add these groups to the group tracker
@@ -349,7 +359,7 @@ function impostors.spawnGroupsFromImpostor(theZone)
theZone.blinkCount = theZone.blinkCount + 1 -- so healthcheck avoids false positives
local args = {}
args.ctry = ctry
args.cat = cfxMX.catText2ID(cat)
args.cat = cat -- cfxMX.catText2ID(cat)
args.rawData = rawData
args.theZone = theZone
args.relinkZones = relinkZones
@@ -384,12 +394,6 @@ function impostors.delayedSpawn(args)
local newGroup = coalition.addGroup(ctry, cat, rawData)
impostors.relinkZonesForGroup(relinkZones, newGroup)
if newGroup then
-- trigger.action.outText("+++ipst: SUCCESS!!! Spawned group <" .. newGroup:getName() .. "> for impostor <" .. rawData.name .. ">", 30)
else
trigger.action.outText("+++ipst: failed to spawn group for impostor <" .. rawData.name .. ">", 30)
end
if theZone.trackWith and groupTracker.addGroupToTrackerNamed then
-- add these groups to the group tracker
if theZone.verbose or impostors.verbose then
@@ -409,7 +413,6 @@ function impostors.delayedCleanup(deadUnits)
end
end
end
--
-- healthCheck
--
@@ -502,8 +505,6 @@ function impostors.update()
end
end
end
--
-- start
--

View File

@@ -1,5 +1,5 @@
persistence = {}
persistence.version = "3.0.1"
persistence.version = "3.0.2"
persistence.ups = 1 -- once every 1 seconds
persistence.verbose = false
persistence.active = false
@@ -22,6 +22,8 @@ persistence.requiredLibs = {
API cleanup
shared text data "flase" typo corrected (no impact)
code cleanup
3.0.2 - more logging
vardump to log possible
PROVIDES LOAD/SAVE ABILITY TO MODULES
PROVIDES STANDALONE/HOSTED SERVER COMPATIBILITY
@@ -165,12 +167,17 @@ function persistence.saveText(theString, fileName, shared, append)
end
function persistence.saveTable(theTable, fileName, shared, append)
net.log("persistence: enter saveTable")
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
net.log("persistence: before json conversion")
local theString = net.lua2json(theTable)
net.log("persistence: json conversion complete")
if not theString then theString = "" end
local path = persistence.missionDir .. fileName
if shared then
@@ -178,6 +185,7 @@ function persistence.saveTable(theTable, fileName, shared, append)
path = persistence.sharedDir .. fileName .. ".txt"
end
net.log("persistence: will now open file at path <" .. path .. ">")
local theFile = nil
if append then
theFile = io.open(path, "a")
@@ -187,8 +195,11 @@ function persistence.saveTable(theTable, fileName, shared, append)
if not theFile then
return false
end
net.log("persistence: will now write file")
theFile:write(theString)
net.log("persistence: will now close file")
theFile:close()
net.log("persistence: will now exit saveTable")
return true
end
@@ -310,6 +321,35 @@ function persistence.missionStartDataLoad()
end
--
-- logging data
--
function persistence.logTable(key, value, prefix, inrecursion)
local comma = ""
if inrecursion then
if tonumber(key) then key = '[' .. key .. ']' else key = '["' .. key .. '"]' end
comma = ","
end
if not value then value = false end -- not NIL!
if not prefix then prefix = "" else prefix = "\t" .. prefix end
if type(value) == "table" then -- recursively write a table
net.log(prefix .. key .. " = \n" .. prefix .. "{\n")
for k,v in pairs (value) do -- iterate all kvp
persistence.logTable(k, v, prefix, true)
end
net.log(prefix .. "}" .. comma .. " -- end of " .. key .. "\n")
elseif type(value) == "boolean" then
local b = "false"
if value then b = "true" end
net.log(prefix .. key .. " = " .. b .. comma .. "\n")
elseif type(value) == "string" then -- quoted string, WITH proccing
value = string.gsub(value, "\\", "\\\\") -- escape "\" to "\\", others ignored, possibly conflict with \n
value = string.gsub(value, string.char(10), "\\" .. string.char(10)) -- 0A --> "\"0A
net.log(prefix .. key .. ' = "' .. value .. '"' .. comma .. "\n")
else -- simple var, show contents, ends recursion
net.log(prefix .. key .. " = " .. value .. comma .. "\n")
end
end
--
-- MAIN DATA WRITE
--
@@ -342,9 +382,10 @@ function persistence.saveMissionData()
-- now handle flags
myData["persistence.flagData"] = persistence.collectFlagData()
net.log("persistence: --- START of module-individual save")
-- now handle all other modules
for moduleName, callbacks in pairs(persistence.callbacks) do
net.log("persistence: invoking save for module " .. moduleName)
local moduleData, sharedName = callbacks.persistData()
if moduleData then
if sharedName then -- save into shared bucket
@@ -358,18 +399,30 @@ function persistence.saveMissionData()
if persistence.verbose then
trigger.action.outText("+++persistence: gathered data from <" .. moduleName .. ">", 30)
end
net.log("persistence: got data for module: " .. moduleName)
--persistence.logTable(moduleName, moduleData)
--net.log("persistence: performing json conversion test for myData")
--local theString = net.lua2json(myData)
--net.log("persistence: json conversion success!")
else
if persistence.verbose then
trigger.action.outText("+++persistence: NO DATA gathered data from <" .. moduleName .. ">, module returned NIL", 30)
end
end
net.log("persistence: completed save for module " .. moduleName)
end
net.log("persistence: --- END of module-individual save")
-- now save data to file
net.log("persistence: will now invoke main saveTable")
persistence.saveTable(myData, persistence.saveFileName)
net.log("persistence: returned from main save table")
-- now save all shared name data as separate files
net.log("persistence: will now iterate shares")
for shareName, data in pairs (allSharedData) do
net.log("persistence: share " .. shareName)
-- save into shared folder, by name that was returned from callback
-- read what was saved, and replace changed key/values from data
local shFile = persistence.sharedDir .. shareName .. ".txt"
@@ -384,12 +437,15 @@ function persistence.saveMissionData()
persistence.saveTable(theData, shareName, true) -- true --> shared
end
net.log("persistence: done iterating shares")
end
--
-- UPDATE
--
function persistence.doSaveMission()
net.log("persistence: start doSaveMission")
-- main save entry, also from API
if persistence.verbose then
trigger.action.outText("+++persistence: starting save", 30)
@@ -407,6 +463,7 @@ function persistence.doSaveMission()
if persistence.saveNotification then
trigger.action.outText("+++persistence: mission saved to\n" .. persistence.missionDir .. persistence.saveFileName, 30)
end
net.log("persistence: DONE doSaveMission")
end
function persistence.noteCleanRestart()

View File

@@ -18,6 +18,7 @@ cfxPlayerScore.firstSave = true -- to force overwrite
3.3.0 - case INsensitivity for all typeScore objects
3.3.1 - fixes for DCS oddity in events after update
- cleanup
TODO: Kill event no longer invoked for map objetcs, attribute
to faction now, reverse invocation direction with PlayerScore
--]]--

View File

@@ -27,6 +27,7 @@ function sweeper.readSweeperZone(theZone)
end
function sweeper.update()
net.log("sweeper: begin update")
timer.scheduleFunction(sweeper.update, {}, timer.getTime() + sweeper.interval)
local toKill = {}
local newFlights = {}
@@ -105,6 +106,8 @@ function sweeper.update()
-- remember new list, forget old
sweeper.flights = newFlights
net.log("sweeper: end update")
end
function sweeper.readConfig()